C++控制对类对象私有成员的访问,通常公有成员方法提供唯一的访问途径。但友元可以突破这一访问限制,其中友元函数虽非成员函数,让其称为类的友元,它的访问权限与公有成员函数相同。当为类重载二元运算符时,常常需要友元。因为不能保证二元运算符的参数都是该类的对象,这将限制该运算符的使用。例如A*B;(其中A是对象,B不是对象)转换为A.operator*(B),但是写成B*A会因为B不是对象无法调用而出错。这时,就可以通过非成员函数——友元函数来解决。
友元函数的原型例子:friend Time operator*(double, const Time &);,注意:
- 虽然友元函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符调用
- 虽不是成员函数,但是与成员函数的访问权限相同。
- 因为不是成员函数,所以编写定义时,不要使用XXX::限定符,不要在定义中使用friend关键字。
对友元函数的理解:友元函数是类的扩展接口的组成部分,与类方法一起实现类接口的两种不同机制,虽然友元函数可以访问私有数据,但是还是由类声明决定哪个函数是友元,因此类声明仍然控制了哪些函数可以访问私有数据,不能认为友元违反OOP数据隐藏的原则。
#include <iostream>
using namespace std;
class Time{
private:
int hours;
int minutes;
public: // 成员函数使用内联函数
Time(int _hours = 0, int _minutes = 0){
hours = _hours;
minutes = _minutes;
}
Time operator*(double n) const;
friend Time operator*(double n, Time & t);
void show()const{
std::cout << hours << " hours," << minutes << " minutes" << std::endl;
}
};
int main(){
Time t1(7,30);
Time t2 = 3 * t1; // 使用了友元函数
Time t3 = t1 * 2; // 使用成员函数
t2.show();
t3.show();
return 0;
}
Time Time::operator*(double n) const
{
Time res;
int min = n * minutes + hours * n * 60;
res.minutes = min % 60;
res.hours = min / 60;
return res;
}
//Time operator*(double n, Time & t){
// Time res;
// int min = n * t.minutes + t.hours * n * 60;
// res.minutes = min % 60;
// res.hours = min / 60;
// return res;
//}
Time operator*(double n, Time & t){ // 1)处
return t * n;
}
1)处: 用友元函数反转操作数顺序,让成员函数处理私有数据
1 常用的友元:重载<< 运算符
首先,<<运算符已经被重载多次了,一是作为位运算符,二是ostream对该运算符进行了重载,将其转换为一个输出工具,cout是一个ostream对象,能够识别c++所有的基本类型。要使cout识别Time对象,一个方法是将一个新的函数运算符定义到ostream类声明中,但修改iostream文件是个危险的主意。但可以通过Time声明让Time类知道如何使用cout。
friend ostream & operator<<(ostream &os, const Time &); // 函数原型
ostream & operator<<(ostream & os, const Time & t){ // 函数定义,注意返回值
os << t.hours <<" hours," << t.minutes << " minutes" << endl;
}
Time类声明使operator<<()函数成为Time类的一个友元函数,但operator<<()函数并不是ostream类的友元,因为函数访问了Time类的私有成员,所以他是Time类的友元。
在重载运算符时,是选择成员函数还是非成员函数?对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载,这样才能直接访问类的私有数据。在重载双目运算符时,主要的区别是,对于成员函数来说,一个操作数通过this指针隐式的传递,另一个通过函数参数显式地传递;对于友元函数来说,两个操作数都作为参数来传递。所以大部分情况下,在避免二义性错误,只能从中选择一个,这时没有多大差别,但某些运算符只能选择成员函数,在某些情况下选择非成员函数更好一些,如为类定义类型转换。