在C++中,友元(Friend) 是一种特殊的访问控制机制,允许一个类或函数访问另一个类的私有和保护成员。尽管它打破了类的封装性,但在特定场景下(如运算符重载、单元测试、数据结构与算法协作等)非常有用。
一、友元函数(Friend Functions)
友元函数是非成员函数,但被授权访问类的私有和保护成员。它的声明需要在类内部使用friend
关键字。
1. 基本语法
class MyClass {
private:
int privateVar;
protected:
int protectedVar;
public:
// 声明友元函数
friend void friendFunction(MyClass& obj);
};
// 友元函数的实现(无需MyClass::前缀)
void friendFunction(MyClass& obj) {
obj.privateVar = 100; // 可以直接访问私有成员
obj.protectedVar = 200; // 可以直接访问保护成员
}
2. 友元函数的特性
- 访问权限:可以访问类的所有私有和保护成员。
- 作用域:友元函数不属于类的成员,因此没有
this
指针。 - 声明位置:友元声明可以放在类的任意位置(
private
/protected
/public
),效果相同。 - 调用方式:像普通函数一样调用,不需要通过对象或类名。
3. 常见应用场景
场景1:运算符重载
class Point {
private:
int x, y;
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
friend Point operator+(const Point& p1, const Point& p2);
};
// 友元函数实现运算符重载
Point operator+(const Point& p1, const Point& p2) {
return Point(p1.x + p2.x, p1.y + p2.y); // 直接访问私有成员
}
场景2:数据结构与算法协作
class Array {
private:
int* data;
int size;
public:
friend void printArray(const Array& arr); // 友元函数用于打印私有数据
};
void printArray(const Array& arr) {
for (int i = 0; i < arr.size; i++) {
cout << arr.data[i] << " "; // 直接访问私有成员
}
}
二、友元类(Friend Classes)
当一个类被声明为另一个类的友元时,友元类的所有成员函数都可以访问被友元类的私有和保护成员。
1. 基本语法
class A {
private:
int secret;
friend class B; // 声明B为友元类
};
class B {
public:
void accessA(A& a) {
a.secret = 42; // B可以访问A的私有成员
}
};
2. 友元类的特性
- 单向性:友元关系不可逆转。若
A
是B
的友元,B
不一定是A
的友元。 - 非传递性:若
A
是B
的友元,B
是C
的友元,A
和C
无友元关系。 - 部分授权:可以只将特定成员函数声明为友元(见下文“成员函数作为友元”)。
3. 常见应用场景
场景1:嵌套类访问外部类
class Outer {
private:
int x;
friend class Inner; // 允许Inner访问Outer的私有成员
public:
class Inner {
public:
void modifyOuter(Outer& outer) {
outer.x = 100; // 直接访问Outer的私有成员
}
};
};
场景2:单元测试框架
class MyClass {
private:
int internalState;
friend class TestMyClass; // 允许测试类访问私有状态
};
class TestMyClass {
public:
void testInternalState() {
MyClass obj;
obj.internalState = 42; // 测试代码直接操作私有成员
}
};
三、成员函数作为友元
可以只将某个类的特定成员函数声明为友元,而非整个类。
class B; // 前向声明
class A {
private:
int data;
friend void B::modifyA(A& a); // B的成员函数作为友元
};
class B {
public:
void modifyA(A& a) {
a.data = 99; // 可以访问A的私有成员
}
};
四、友元的注意事项
- 打破封装性:过度使用友元会破坏类的封装原则,建议仅在必要时使用。
- 替代方案:优先考虑使用公有接口(如getter/setter),而非友元。
- 编译依赖:友元声明不会影响访问权限的检查,但可能导致编译依赖问题(如前向声明)。
- 命名空间:若友元函数未在全局作用域声明,编译器会隐式声明它。
五、友元与继承
- 友元关系不可继承:基类的友元不能自动访问派生类的私有成员。
- 派生类的友元:若派生类声明了友元,该友元只能访问派生类的私有成员,无法访问基类的私有成员。
六、对比:友元函数 vs 成员函数
特性 | 友元函数 | 成员函数 |
---|---|---|
访问权限 | 可访问私有/保护成员 | 可访问私有/保护成员 |
this指针 | 无 | 有 |
调用方式 | 普通函数调用 | 通过对象或类名调用 |
重载运算符 | 可重载某些成员函数无法重载的运算符(如<< ) | 依赖对象调用 |