C++控制对类对象私有部分的访问。通常,公有类方法提供唯一的访问途径,但有时候这种限制太严格,以致于不适合特定的编程问题。在这种情况下,C++提供了另外一种形式的访问权限:友元。友元有3种:友元函数,友元类,友元成员函数。
为什么需要友元?
Time Time::operator*(double mult)const { Time result; long totalminutes = hours * mult * 60 + minutes * mult; result.hours = totalminutes / 60; result.minutes = totalminutes % 60; return result; }
Time A, B; A = B * 2.75; //A = B.operator*(2.75); A = 2.75 * B; //cannot correspond to a member function
解决这个问题的一种方式—非成员函数(大多数运算符都可以通过成员或非成员函数来重载)。非成员函数不是由对象调用,它使用的所有值(包括对象)都是显式参数。如该函数原型为Time operator*(double m, const Time &t)。使用非成员函数可以按所需的顺序获得操作数,但非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问。然而有一类特殊的非成员函数可以访问类的私有成员—友元函数。
创建友元:
创建友元的第一步是将其原型放在类声明中,并在原型声明前加上friend,如
friend Time operator*(double mult, const Time &t);
这意味着operator*()函数虽然是在类声明中声明的,但它不是成员函数,不能使用成员运算符来调用。operator*()函数不是成员函数,但它与成员函数的访问权限相同。
第二部编写原型。注意不使用Time:: 限定符,在定义中不使用关键字friend。
Time operator*(double mult, const Time &t) { Time result; long totalminutes = t.hours * mult * 60 + t.minutes * mult; result.hours = totalminutes / 60; result.minutes = totalminutes % 60; return result; }
所以 A = 2.75 * B; 将转换为 A = operator*(2.75, B); 从而调用刚才定义的友元函数。
总之,类的友元函数是非成员函数,其访问权限与成员函数相同。
常用的友元:重载<<运算符
1、<<的第一种重载版本
要使Time类知道使用cout<<trip,必须使用友元函数。因为cout是ostream类对象,如果使用一个Time成员函数来重载<<,Time对象将是第一个操作数,使用<<时就变成了trip<<cout,但通过使用友元函数就可以使用cout<<trip;
void operator<<(ostream &os, const Time &t) { os << t.hours << " hours, " << t.minutes << " minutes"; }
新的Time类声明使operator<<()函数成为Time类的一个友元,但该函数不是ostream类的友元。因为operator<<()直接访问Time对象的私有成员,所以它必须是Time类的友元。但由于它并不直接访问ostream对象的私有成员,所以不一定必须是ostream类的友元。
调用cout<<trip应使用对象本身,而不是它的拷贝,因此该函数按引用(而不是按值)来传递该对象。这样cout<<trip将导致os成为cout的一个别名。Time对象可以按值或者按引用来传递,因为这两种方式都使函数能够使用对象的值。按引用传递使用的内存和时间都比按值传递少。
2、<<的第二种重载版本
第一种版本存在一个问题。对于cout<<trip;(trip是Time类对象)可以正常工作,但这种实现不允许想通常那样将重新定义的<<运算符与cout一起使用。
cout << "Trip time: " << trip << " (Tuesday)\n"; //can't do
这是因为第一种版本的operator<<()函数返回值为void,而运算符<<要求左边是ostream对象。
拿cout<<x<<y;这个例子来说(int x=5,y=8;)。ostream类将operator<<()函数实现为返回一个指向ostream对象的引用。也就是说,它返回一个指向调用对象(这里是cout)的引用。因此表达式(cout<<x)本身就是ostream对象cout,从而可以位于运算符的左侧。
可以对友元函数采用相同的方法,只需修改operator<<()函数,让它返回ostream的引用即可。
ostream & operator<<(ostream & os, const Time &t) { os << t.hours << " hours, " << t.minutes << " minutes"; return os; }
友元是否有悖于OOP?
友元是否违反了OOP数据隐藏的原则,因为友元机制允许非成员函数访问私有数据,但这种观点太片面了。相反,应将友元函数看作类的扩展接口的组成部分。例如,从概念上看,double乘以Time和Time乘以double是完全相同的。前一个要求有友元函数,后一个使用成员函数,这是C++句法的结果,而不是概念的差别。通过使用友元函数和类方法,可以用一个用户接口表达这两种操作。另外,只有类声明可以决定哪一个函数是友元,因此类声明仍然控制了哪些函数可以访问私有数据。总之,类方法和友元只是表达类接口的两种不同机制。