友元函数声明必须在类内,定义在类外且不加friend关键字;它不是成员函数,无this指针,访问私有成员需显式传参。

友元函数声明必须写在类内部,但定义可以放在外面
友元不是类的成员,它只是被“授权”访问私有成员的普通函数。所以声明要放在 class 里(通常在私有区或公有区都行),但定义和普通函数一样,写在类外,甚至可以放在另一个源文件里。
常见错误是把定义也塞进类体内——这会让编译器以为是内联函数,而且容易引发重复定义问题(尤其头文件被多处包含时)。
- 声明格式固定:
friend 返回类型 函数名(参数列表); - 定义时**不要加
friend关键字**,否则报错:error: 'friend' used outside of class - 如果函数需要访问多个类的私有成员,可以在每个类里分别声明一次
friend
友元函数没有 this 指针,所有对象都得显式传参
这是最容易忽略的差异:成员函数隐式带 this,而友元函数完全不绑定对象。哪怕你只打算操作一个对象,也得把它作为参数传进去,否则根本拿不到私有成员。
比如想打印某个 Person 对象的 age(私有),不能写 cout ,必须写 <code>cout ,其中 <code>p 是传入的 Person& 参数。
立即学习“C++免费学习笔记(深入)”;
- 参数类型建议用 const 引用(
const Person&),避免拷贝,也防止误改 - 如果要修改对象,就去掉
const,但注意这违背了“只读观察”的常见使用意图 - 两个对象交互?那就传两个参数:
friend bool operator==(const A&, const A&);
友元破坏封装性,但 operator 和 <code>operator>> 几乎必须用它
流操作符重载必须是全局函数(因为左操作数是 std::ostream& 或 std::istream&),又必须访问类私有成员——这时候不用友元,根本没法实现干净的输出/输入接口。
其他场景要谨慎:比如本可以用 getter 实现的功能,硬上友元,等于主动打开后门。调试用的打印函数、序列化辅助函数是合理使用场景;业务逻辑里频繁出现 friend,大概率是设计在退化。
operator 第一个参数必须是 <code>std::ostream&,第二个是你的类类型,返回std::ostream&- 别忘了在函数体里 return
os,否则链式调用(如cout )会断掉 - 头文件中声明友元后,记得确保定义可见(比如实现在 .cpp 里,就要保证链接时能找得到)
模板类的友元函数容易链接失败,得用模板声明+特化匹配
给 template<class t> class Box</class> 声明友元函数时,如果直接写 friend void print(const Box&);,编译器不知道 Box 是哪个实例化版本,链接时大概率报 undefined reference。
正确做法是让友元本身也变成模板,并在类内声明时指定模板参数,或者用“友元模板”语法提前声明。
- 简单方案:在类内写
friend void print(const Box<t>&);</t>,然后在类外定义template<typename t> void print(const Box<t>& b) { ... }</t></typename> - 更通用方案:在类前加
template<typename u> friend void print(const Box<u>&);</u></typename>,这样所有Box<int></int>、Box<:string></:string>都能匹配 - 别忘了:函数模板定义通常得放在头文件里,否则同样链接失败
friend 可能让后续所有改动都踩在薄冰上。











