C++ 友元(friend)详解
友元(friend)是 C++ 提供的一种「定向开权限」机制:允许类外部的函数或另一个类访问本类的 private / protected 成员。本文介绍友元的三种形式、为什么需要它,以及使用时的注意事项。
一、友元是什么
友元函数不是类的成员函数,却可以访问类的 private / protected 成员。
三个关键认知:
- 不是成员函数:它属于全局作用域(或另一个类),不在本类作用域内;
- 没有 this 指针:因为不属于任何对象,想访问某个对象的成员就必须把它作为参数传进来;
- 权限是「授予」的:由被访问的类自己用
friend声明,主动权在被访问者手里,而不是想访问的人自己宣布。
二、友元函数
语法是类内声明 + 类外定义:在类里加 friend 声明,函数体写在类外面。
1 |
|
注意 show 是靠参数 obj 拿到对象的——它没有 this,所以这个参数不能省。
三、为什么需要友元:重载 <<
友元最典型的用途是重载运算符,尤其是 <<。
如果把它写成成员函数,<< 的左操作数就必须是本类对象;但输出语句 cout << obj 的左操作数 cout 是 ostream,根本不是我们的类——成员函数形式表达不了,只能写成全局函数。而全局函数若要读类的私有成员,就得声明为友元:
1 |
|
operator<<为什么必须返回ostream&,见 C++ 运算符重载详解。
四、友元类
授予另一个类访问权限,写法是 friend class 类名;:
1 |
|
注意方向:是 A 把 B 声明为友元,而不是 B 宣布自己能访问 A。这再次体现了主动权在被访问的类手里。
五、友元成员函数
如果只想开放另一个类的某一个成员函数(颗粒度更细),可以这样写:
1 |
|
这里的书写顺序有讲究:friend void B::show(const A& a); 要求 B 已经完整定义,而 B::show 的函数体又要求 A 已经完整定义,所以只能「前置声明 A → 定义 B → 定义 A → 定义 B::show」。
六、友元的三条性质
- 单向:A 声明 B 是友元,只意味着 B 能访问 A;反过来不行,除非 A 也被 B 声明为友元。
- 不能被继承:基类的友元不会自动成为派生类的友元,也不会自动获得访问派生类成员的权限。
- 不具有传递性:B 是 A 的友元、C 是 B 的友元,不代表 C 能访问 A。
七、友元与封装
友元确实破坏了封装——它把 private 撕开一个口子。一旦外部代码依赖了这些私有成员,以后想改内部结构就被绑住了。所以:
- 能不用就不用。能用公有成员函数(getter)解决的,就别开友元。
- 少数确实合理的情况:
- 重载
operator<</operator>>(成员函数形式表达不了); - 两个类需要紧密协作(如容器与迭代器);
- 逻辑上属于本类、但语法上不能作为成员的工厂函数。
- 重载
八、小结
- 友元函数不是成员函数,没有 this 指针,访问对象成员要靠参数传;
- 语法:类内
friend声明,定义写在类外; - 三种形式:友元函数
friend void f();、友元类friend class B;、友元成员函数friend void B::f();; - 友元是单向、不可继承、不可传递的;
- 最典型的用途是重载
operator<<;友元破坏封装,能不用就不用。
