FX-友元函数和友元类
友元函数和友元类是C++中的特性,允许外部函数或类访问某个类的私有(private)和保护(protected)成员。它们通过破坏封装性来提供灵活性,通常用于特定场景。
1. 友元函数
友元函数是一个非成员函数,但可以访问类的私有和保护成员。它在类中声明,并使用 friend
关键字。
示例:
class MyClass {
private:
int secret;
public:
MyClass(int s) : secret(s) {}
// 声明友元函数
friend void displaySecret(MyClass obj);
};
// 定义友元函数
void displaySecret(MyClass obj) {
// 可以直接访问私有成员
std::cout << "Secret is: " << obj.secret << std::endl;
}
int main() {
MyClass obj(42);
displaySecret(obj); // 输出: Secret is: 42
return 0;
}
2. 友元类
友元类是一个类,其所有成员函数都可以访问另一个类的私有和保护成员。它在类中声明,并使用 friend
关键字。
示例:
class MyClass {
private:
int secret;
public:
MyClass(int s) : secret(s) {}
// 声明友元类
friend class FriendClass;
};
class FriendClass {
public:
void displaySecret(MyClass obj) {
// 可以直接访问私有成员
std::cout << "Secret is: " << obj.secret << std::endl;
}
};
int main() {
MyClass obj(42);
FriendClass fc;
fc.displaySecret(obj); // 输出: Secret is: 42
return 0;
}
关键点
-
访问权限:友元函数或类可以访问类的私有和保护成员。
-
单向性:友元关系是单向的,若
A
是B
的友元,B
不自动成为A
的友元。 -
不传递:友元关系不传递,若
A
是B
的友元,B
是C
的友元,A
不自动成为C
的友元。 -
破坏封装:友元破坏了封装性,应谨慎使用。
适用场景
-
需要外部函数或类访问私有成员时。
-
运算符重载时,如
<<
或>>
,通常需要声明为友元函数。
在C++中,重载 <<
运算符(用于输出流)通常需要将其声明为友元函数,原因如下:
1. 运算符重载的两种形式
C++ 中运算符重载可以通过两种方式实现:
-
成员函数:运算符作为类的成员函数。
-
非成员函数:运算符作为全局函数或友元函数。
对于 <<
运算符,通常需要将其重载为非成员函数,原因如下。
2. <<
运算符的特殊性
<<
是用于输出流的运算符,通常与 std::ostream
对象(如 std::cout
)一起使用。它的调用形式通常是:
std::cout << object;
其中:
-
std::cout
是std::ostream
类型的对象。 -
object
是用户自定义类的对象。
如果 <<
重载为类的成员函数,调用形式会变为:
object << std::cout;
这与常规用法不符,也不符合直觉。
3. 为什么需要友元函数?
为了将 <<
重载为非成员函数,同时又能访问类的私有成员,需要将其声明为友元函数。原因如下:
-
访问私有成员:
<<
运算符通常需要访问类的私有数据成员以输出其内容。 -
非成员函数:
<<
需要作为非成员函数重载,以保持std::cout << object
的调用形式。
通过声明为友元函数,<<
可以在类外部访问私有成员,同时保持非成员函数的形式。
4. 示例代码
以下是一个典型的 <<
运算符重载示例:
#include <iostream>
using namespace std;
class MyClass {
private:
int value;
public:
MyClass(int v) : value(v) {}
// 声明友元函数
friend ostream& operator<<(ostream& os, const MyClass& obj);
};
// 定义友元函数
ostream& operator<<(ostream& os, const MyClass& obj) {
os << "MyClass value: " << obj.value; // 访问私有成员 value
return os;
}
int main() {
MyClass obj(42);
cout << obj << endl; // 输出: MyClass value: 42
return 0;
}
5. 关键点总结
-
非成员函数:
<<
需要作为非成员函数重载,以保持std::cout << object
的调用形式。 -
访问私有成员:通过声明为友元函数,
<<
可以访问类的私有成员。 -
灵活性:友元函数提供了灵活性,同时保持了封装性(仅在必要时破坏封装)。
6. 如果不使用友元函数?
如果不使用友元函数,可以通过提供公有成员函数来获取私有数据,然后在 <<
重载中使用这些函数。例如:
class MyClass {
private:
int value;
public:
MyClass(int v) : value(v) {}
int getValue() const { // 提供公有成员函数
return value;
}
};
ostream& operator<<(ostream& os, const MyClass& obj) {
os << "MyClass value: " << obj.getValue(); // 通过公有函数访问私有成员
return os;
}
这种方法避免了使用友元函数,但需要额外编写公有接口,可能会增加代码复杂性。
总结
重载 <<
运算符通常需要声明为友元函数,因为它需要作为非成员函数重载以保持调用形式的直观性,同时需要访问类的私有成员。友元函数提供了实现这一需求的简洁方式。
友元函数和友元类提供了访问私有和保护成员的途径,但应谨慎使用以避免破坏封装性。