友元
基本概念
在C++面向对象编程中,封装是核心特性之一。类通过private和protected访问控制符,将内部数据和实现细节隐藏起来,仅暴露public接口与外部交互,以此保证数据安全和代码的可维护性。但在某些场景下,这种严格的封装可能带来不便——比如两个紧密协作的类需要互相访问对方的内部状态,或者某个外部函数需要直接操作类的私有成员以实现特定功能(如运算符重载)。此时,友元机制便提供了一种灵活的解决方案,它允许特定的外部实体临时获得访问类私有成员的权限,成为类的“特殊访客”。
友元可以是函数,也可以是类,甚至可以是另一个类的某个成员函数。它们的共同特点是,一旦被类声明为友元,就能够绕过访问控制符的限制,直接访问类中的private和protected成员。这种机制并非破坏封装的设计,而是在保证整体封装性的前提下,为特殊场景提供的灵活接口。
外部函数的特殊访问权
普通函数可以被声明为友元函数。要将一个函数声明为类的友元,只需在类的内部(通常是public区域,也可在private或protected区域,位置不影响权限)使用friend关键字声明该函数即可。
例如,假设有一个表示点的Point类,包含私有成员x和y,若需要一个外部函数printPoint来打印这两个坐标,就可以将printPoint声明为Point的友元:
1 | class Point { |
在这个例子中,printPoint并非Point类的成员函数,但因为被声明为友元,所以能直接访问Point的私有成员x和y。需要注意的是,友元函数的声明仅指定了它的访问权限,函数的定义仍需在类外部进行,且定义时不需要再添加friend关键字。
运算符重载
运算符重载是友元函数的常见应用场景。例如,重载输出运算符<<时,由于运算符的左操作数是ostream对象(如std::cout),而非当前类的对象,因此无法将其定义为类的成员函数(成员函数的左操作数默认是this指针指向的对象)。这时,将重载函数声明为类的友元,就能让它访问类的私有成员,从而完成输出操作:
1 | class Student { |
友元类
除了函数,一个类也可以被声明为另一个类的友元类。当类A被声明为类B的友元时,类A的所有成员函数都拥有访问类B的private和protected成员的权限。这种机制适用于两个类关系非常紧密,需要互相深入访问内部状态的场景。
例如,一个Teacher类需要管理多个Student对象的成绩细节,而成绩可能是Student的私有成员,此时可将Teacher声明为Student的友元类:
1 | class Student { |
1 | John's score: 85 |
友元类的声明同样使用friend关键字,在目标类内部声明“friend class 类名;”即可。需要注意的是,友元关系是单向的——如果Teacher是Student的友元,并不意味着Student是Teacher的友元,Student无法访问Teacher的私有成员。同时,友元关系没有传递性,若类A是类B的友元,类B是类C的友元,类A不会自动成为类C的友元。
友元成员函数
还可以将某个类的特定成员函数声明为友元,而非整个类,这种方式称为友元成员函数。相比友元类,它能更精确地控制权限,避免过度开放访问权限。
例如,只需让Teacher类的adjustScore函数访问Student的私有成员,而printStudentScore函数不需要,则可单独声明adjustScore为友元:
1 | // 提前声明Student类,因为Teacher的成员函数参数需要用到Student |
使用友元成员函数时,需要注意类的声明顺序:由于友元成员函数属于另一个类,必须先声明该类及其成员函数,再在目标类中声明友元关系,否则编译器会无法识别函数所属的类。
特性与注意事项
不具有继承性:友元关系不能被继承,即如果类A是类B的友元,类B的子类C不会自动获得类A的友元权限,类A也不能访问C中新增的私有成员(除非C单独声明A为友元)。
不具有对称性:A是B的友元,并不意味着B是A的友元。
不具有传递性:A是B的友元,B是C的友元,但A不是C的友元。
友元声明不会影响访问控制符的原有作用,它只是额外授予权限,类的其他成员仍受private、protected、public的限制。
此外,友元的声明位置在类内的public、private或protected区域均可,位置不影响友元的权限,通常为了代码清晰,会放在public区域。
一个常规的成员函数声明描述了三件在逻辑上互不相同的事情:
- 该函数能够访问类声明的私有部分
- 该函数位于类的作用域之中
- 该函数必须经由一个对象去激活(有一个this指针)
将一个函数声明为友元可以使他具有第一种性质
将一个函数声明为static可以使他具有第一种和第二种性质




