友元
C++中友元分为:友元函数和友元类
封装是面向对象的三个基本特征之一,C++中通过类的概念将功能封装在内部,只对外提供 api 接口。而友元提供了一种突破封装的方式,这有时提供了便利,但突破封装意味着耦合度的增加,所以友元需要“酌情使用”。
友元函数
我们知道,C++ 的运算符重载函数的参数中,有一个隐藏的 this
指针,这通常代表了这个操作符的第一个操作数,例如下面这个例子:
class cls {
public:
int operator+(int num) {}
}
加号有两个操作数,左操作数对应隐含的 this
指针,右操作数才对应参数列表中的 num
。但这带来了一个限制,即类对象必须是这个运算符的左操作数才能触发重载的运算符,例如
int main() {
cls obj;
cout << obj + 1 << endl; // 正确,类对象在操作符左边
cout << 1 + obj << endl; // 错误,类对象在操作符右边
}
这个限制在大多数时候没什么关系,毕竟 1 + obj
和 obj + 1
没啥区别,但对于两个特殊的操作符来说,这就很严重了。
输入运算符 >>
和输出运算符 <<
就不同寻常,他们的左操作数通常是该运算符要读取的流的引用,例如 cout << 123
和 cin >> a
。所以如果你想重载你自己的类的输入输出运算符就很难办到。一个解决办法是声明一个全局的输入输出运算符重载函数,这样你就可以自由的控制参数的顺序,例如这样:
class cls {
public:
int a;
};
ostream &operator<<(ostream &os, cls _obj) {
os << _obj.a;
}
但这样的限制在于,全局的运算符重载函数没有 this
指针,所以不能访问私有属性,这就导致很难完成输入输出的任务,除非你像上面一样把属性定义成公有的,但这很明显不符合常理。
所以最终的解决方案就是友元函数,友元函数可以让一个非成员函数访问类的私有成员。
class cls {
friend ostream &operator<<(ostream &os, cls &obj);
friend istream &operator>>(istream &is, cls &obj);
public:
cls(int _a, int _b) {
a = _a;
b = _b;
}
private:
int a, b;
};
ostream &operator<<(ostream &os, cls &obj) {
os << obj.a << "-" << obj.b << endl;
return os;
}
istream &operator>>(istream &is, cls &obj) {
is >> obj.a >> obj.b;
return is;
}
int main() {
cls obj(1, 2);
cout << obj;
cin >> obj;
cout << obj;
return 0;
}
如代码所示,友元函数通过 friend
关键字指定,在这个类中通过 friend
关键字指定了两个友元函数,分别是输入运算符的重载和输出运算符的重载。则此时这两个重载函数虽然不是类的成员函数,但却可以访问类对象的私有成员。
友元函数有下面这些特性:
- 友元函数可以访问类的私有成员,但它不是类的成员函数。
- 友元函数不能用
const
修饰,原因显而易见,友元函数没有this
指针。 - 友元函数可以在类定义的任何地方声明,不受访问限定符限制。
- 一个函数可以是多个类的友元函数。
友元类
友元类的概念类似友元函数,假如一个类是另一个类的友元类,则相当于这个类中的所有成员函数都是另一个类的友元函数。
class Date; // 前置声明
class Time {
friend class Date; // 声明 Date 类为 Time 类的友元类,则相当于 Date 类中所有的成员函数都是 Time 类的友元函数
public:
Time(int hour, int minute, int second)
: _hour(hour)
, _minute(minute)
, _second(second) {}
private:
int _hour, _minute, _second;
};
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _minute(minute) {}
void SetTimeOfDate(int hour, int minute, int second) {
// 直接访问 Time 类的私有成员
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year, _month, _day;
Time _t;
}
- 友元类是单向的,不具有交换性。上面的例子中,
Date
类是Time
类的友元类,则Date
类中的函数可以直接访问Time
类的私有成员。但Time
类不是Date
类的友元类。 - 友元关系不能传递。如果 B 是 A 的友元类,C 是 B 的友元类,不代表 C 是 A 的友元类。