126.友元函数在类内部声明还是内外?
126.友元函数在类内部声明还是内外?
1.什么是友元
- 友元是一种授权机制:允许外部函数 / 其他类,访问某个类的 private /protected 成员。
- 友元不是类的成员,只是被 “破例允许访问”。
2.友元函数
友元函数是 C++ 中一种打破类封装限制的机制,允许一个非成员函数访问类的 private 或 protected 成员。
2.1 核心概念
- 友元函数不是类的成员函数,但被类授予特殊访问权限。
- 它可以像普通函数一样调用,只是能访问类的私有 / 保护成员。
- 友元关系是单向的:类 A 把函数 f 声明为友元,不代表 f 所在的作用域也能访问 A 的其他成员。
2.2 声明与定义
(1)声明位置
友元函数的声明必须放在类内部(可以在 public/private/protected 任意区段,位置不影响访问权限):
class MyClass {
private:
int data;
public:
MyClass(int d) : data(d) {}
// 声明友元函数:可以访问 MyClass 的私有成员
friend void printData(const MyClass& obj);
};
(2)定义位置
友元函数的定义必须放在类外部(全局作用域或命名空间内):
// 友元函数定义:可以直接访问 obj.data
void printData(const MyClass& obj) {
std::cout << "Data: " << obj.data << std::endl;
}
(3)调用方式
和普通函数一样调用,不需要通过对象:
int main() {
MyClass obj(42);
printData(obj); // 直接调用友元函数
return 0;
}
2.3常见使用场景
场景 1:重载流插入 / 提取运算符(operator<< / operator>>)
这是友元函数最经典的用途 —— 因为运算符重载必须是非成员函数才能让左操作数是流对象(如 std::cout)。
重载流插入 / 提取运算符定义成友元的原因
必须用友元,根本原因是:流运算符重载时,流对象(cout/cin)必须在左边,而 this 指针默认是左操作数,冲突了,所以只能写成非成员函数;写成非成员又要访问私有成员,就必须用友元。
- 左操作数是流对象:ostream / istream
- 右操作数是自定义类对象
class Point {
private:
int x, y;
public:
Point(int x_, int y_) : x(x_), y(y_) {}
// 友元重载 << 运算符,用于输出 Point 对象
friend std::ostream& operator<<(std::ostream& os, const Point& p);
};
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
// 使用:
Point p(3, 4);
std::cout << p << std::endl; // 输出 (3, 4)
场景 2:两个类之间的协作函数
当两个类需要紧密交互时,友元函数可以避免暴露过多 public 接口:
class B; // 前向声明
class A {
private:
int a;
public:
A(int a_) : a(a_) {}
friend int addAandB(const A& aObj, const B& bObj);
};
class B {
private:
int b;
public:
B(int b_) : b(b_) {}
friend int addAandB(const A& aObj, const B& bObj);
};
// 友元函数可以同时访问 A 和 B 的私有成员
int addAandB(const A& aObj, const B& bObj) {
return aObj.a + bObj.b;
}
场景 3:模板类的友元函数
模板类中声明友元函数需要注意语法:
template <typename T>
class MyTemplate {
private:
T data;
public:
MyTemplate(T d) : data(d) {}
// 友元函数模板
template <typename U>
friend void printTemplate(const MyTemplate<U>& obj);
};
template <typename U>
void printTemplate(const MyTemplate<U>& obj) {
std::cout << obj.data << std::endl;
}
2.4 关键特性与注意事项
(1)友元关系的特性
- 单向性:类 A 把函数 f 声明为友元,f 能访问 A 的私有成员,但 A 不能访问 f 所在类的私有成员(除非 f 所在类也把 A 声明为友元)。
- 不传递性:A 是 B 的友元,B 是 C 的友元 ≠ A 是 C 的友元。
- 不继承性:子类不会继承父类的友元函数权限,父类的友元函数不能直接访问子类的私有成员。
(2)封装与友元的平衡
- 友元函数破坏了类的封装性,应尽量少用。
- 优先使用 public 接口,只有在必须访问私有成员(如运算符重载、紧密协作类)时才使用友元。
(3)声明与定义的顺序
- 如果友元函数需要访问类的成员,类的定义必须在友元函数的定义之前。
- 可以先在类内声明友元函数,再在类外定义。
2.5 友元函数 vs 成员函数
| 特性 | 友元函数 | 成员函数 |
|---|---|---|
| 是否属于类 | ❌ 不是类的成员 | ✅ 是类的成员 |
| 访问权限 | ✅ 可访问类的 private/protected | ✅ 可访问类的所有成员 |
| 调用方式 | 普通函数调用:func(obj) | 对象调用:obj.func()或ptr->func() |
| this指针 | ❌ 没有this指针 | ✅ 隐含this指针 |
| 用途 | 运算符重载、跨类协作、特殊访问 | 类的核心行为与操作 |
2.6 一句话总结
友元函数是类的 “特殊访客”,不是成员却能访问私有成员;常用于运算符重载和跨类协作,需谨慎使用以避免破坏封装。

浙公网安备 33010602011771号