友元
更新时间:2026-08-27。本文是
languages/cpp/主题入门层第 23 篇,类与对象子族收尾。friend是封装的一道例外口子:把private的钥匙借给指定的函数或类。它最容易让人疑惑的是"那封装还有什么意义",本篇把它的正当用途和不正当用法都说清楚。
本文要回答的问题
friend怎么声明?借出去的是什么?- 友元最正当的用途是什么?
- 友元会破坏封装吗?什么时候才该用?
一、friend:把 private 的钥匙借出去
class Point {
int x_, y_;
public:
Point(int x, int y) : x_(x), y_(y) {}
friend double distance(const Point& a, const Point& b);
};
double distance(const Point& a, const Point& b) {
// 普通函数,但能访问 Point 的私有成员 x_ y_
return std::sqrt((a.x_ - b.x_) * (a.x_ - b.x_) +
(a.y_ - b.y_) * (a.y_ - b.y_));
}friend double distance(...) 声明在类里,意思是"distance 这个函数是我的朋友,允许它碰我的私有成员"。注意三件事:
friend声明放在private:还是public:段都行——它是"朋友名单",不参与访问控制;- 被声明的函数不是成员函数,就是普通全局函数;
- 友元关系是单向的、不可继承的——你把它当朋友,它未必把你当朋友;朋友的"朋友的朋友"不算朋友。
二、友元最正当的用途:运算符重载
为什么需要友元?看流输出运算符 operator<<:
std::cout << p;operator<< 的左边是 std::cout(ostream),右边是 Point。它没法写成 Point 的成员函数(成员函数要求左边是 Point),只能写成全局函数。而它又要读 x_、y_——私有成员。于是:
class Point {
int x_, y_;
public:
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x_ << ", " << p.y_ << ")";
return os;
}
};这就是友元用得最多的地方——全局运算符重载需要访问私有成员(下一篇运算符重载的必备搭档)。同理还有 operator>>、operator+(两个对象相加,写成员函数只能左边是对象、右边是别的)等。
三、友元类:整类都放行
friend class B; 把 B 整个类都变成朋友,B 的所有成员函数都能访问 A 的私有成员:
class Node; // 前置声明
class LinkedList {
friend class Node; // Node 可以摸 LinkedList 的内部
Node* head_;
};典型场景:两个类强耦合、必须互相访问内部(链表和节点、树和节点、迭代器)。友元类的缺点是太"大"——一开就是整类放行,能不用尽量不用,单个友元函数比友元类更克制。
四、友元破坏封装吗?
看你怎么用。友元不是"开放",是"定向授权":
- 封装防的是"无差别的乱摸"——
private挡住所有外人; - 友元是"指名道姓地开一个后门"——只有名单上的能进,名单公开写在类里,谁都能看见谁有这个权限;
- 真正破坏封装的写法是"到处 friend"——把半个程序都加进朋友名单,那
private就形同虚设了。
经验法则:友元用于"语言机制要求"(运算符重载、迭代器),不用来解决"设计偷懒"(懒得写 getter)。设计上该用接口的就用接口,friend 是给语言机制准备的钥匙。
五、和 C 对比
| 对比项 | C | C++ |
|---|---|---|
| 私有访问例外 | 无私有概念 | friend 定向授权 |
| 运算符重载 | 无 | 友元是全局重载的关键 |
| 强耦合类互访 | 全靠公开或封装指针 | 友元类 |
| 滥用风险 | — | 到处 friend = 封装失效 |
C 里两个结构体要互相操作私有数据,要么全公开(无封装可言),要么用不透明指针绕(void* + 函数接口)。C++ 的友元给了"协作但要定向"的中间选项,代价是要管住自己别滥用。
六、与本站主线衔接
- 下一篇是运算符重载入门:
operator<<、operator+这些重载怎么写,友元是主角之一,见 运算符重载入门; - 封装与访问控制的完整语境,回看 public / private / protected;
- 友元与继承的关系(友元不继承),见 继承基础。
一句话总结
friend 是封装的一道定向后门:声明在类里,把 private 的访问权只借给指名道姓的函数或类(单向、不继承、不影响访问控制段位);它最正当的用途是全局运算符重载和强耦合类互访,别拿它替代 getter——到处 friend 才是真破坏封装。