C++ 教材(《C++ Primer》第5版)章节标题为:
7.2.1 友元 (friend)
本节核心内容是:当类的数据成员设为 private 时,如何让非成员函数(如 read, print, add)能够访问这些私有成员?答案是——使用 friend 关键字声明“友元函数”。
这是面向对象设计中“封装性”与“接口灵活性”之间的重要平衡机制。
🔍 逐段解析
✅ 第一段:问题背景
既然 Sales_data 的数据成员是 private 的,我们的 read、print 和 add 函数也就无法正常编译了,这是因为尽管这几个函数是类的接口的一部分,但它们不是类的成员。
💡 核心要点:
- 如果将
bookNo,units_sold,revenue设为private(推荐做法),那么外部函数(包括非成员函数)默认无法访问。 - 但
read,print,add在逻辑上属于类的接口,需要访问这些数据。 - 矛盾:封装性 vs 接口需求
🧩 解决方案:
类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元(friend)。
—— 这就是 friend 关键字的作用:打破封装限制,授予特定函数/类访问权限。
✅ 第二段:语法说明
如果类想把一个函数作为它的友元,只需要增加一条以 friend 关键字开始的函数声明语句即可:
class Sales_data {
// 为 Sales_data 的非成员函数所做的友元声明
friend Sales_data add(const Sales_data&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
friend std::ostream &print(std::ostream&, const Sales_data&);
// 其他成员及访问说明符与之前一致
public:
// ... 构造函数和公有成员函数
private:
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// Sales_data 接口的非成员组成部分的声明
Sales_data add(const Sales_data&, const Sales_data&);
💡 关键语法点:
-
friend声明位置:- 可以在
public、protected或private区域声明,不影响访问权限。 - 通常放在类定义开头或结尾,便于阅读。
- 可以在
-
friend声明形式:- 就像普通函数声明,前面加
friend关键字。 - 不需要函数体(只是声明)。
- 参数类型必须与实际定义完全匹配。
- 就像普通函数声明,前面加
-
友元关系是单向的:
Sales_data声明read是友元 →read可以访问Sales_data的私有成员。- 但
read不能反过来让Sales_data访问自己的私有成员(除非也声明)。
-
友元不传递、不继承:
- A 是 B 的友元,B 是 C 的友元 ≠ A 是 C 的友元。
- 派生类不会自动继承基类的友元关系。
-
友元声明 ≠ 定义:
- 类内的
friend只是声明,函数定义仍需在类外单独写出。 - 注意最后一行:
—— 这是在类外再次声明该函数(因为它是非成员函数),确保链接器能找到它。Sales_data add(const Sales_data&, const Sales_data&);
- 类内的
🎯 为什么需要友元?
| 场景 | 为什么不用成员函数? | 为什么用友元? |
|---|---|---|
输入输出操作(read, print) | 第一个参数是流对象 (istream/ostream),不是 Sales_data 本身 | 需要访问私有成员,但不适合做成成员函数 |
对称二元运算(add, operator+) | 两个参数地位平等,若做成成员函数,左边必须是 Sales_data 对象 | 友元函数可自由安排参数顺序,更自然 |
| 工具函数(如比较、转换等) | 不属于类的核心行为,不应污染类接口 | 保持类简洁,同时提供必要功能 |
⚠️ 易错点提醒
错误1:忘记在类外重新声明非成员函数
class Sales_data {
friend Sales_data add(const Sales_data&, const Sales_data&);
// ...
};
// ❌ 错误:缺少类外声明!编译器可能找不到这个函数
Sales_data add(const Sales_data& lhs, const Sales_data& rhs) {
// ...
}
✅ 正确做法:
class Sales_data {
friend Sales_data add(const Sales_data&, const Sales_data&);
// ...
};
// ✅ 必须在类外再次声明(尤其在头文件中)
Sales_data add(const Sales_data&, const Sales_data&);
// 然后在 .cpp 文件中定义
Sales_data add(const Sales_data& lhs, const Sales_data& rhs) {
// ...
}
📌 注意:有些编译器允许省略类外声明,但为了可移植性和清晰性,强烈建议显式声明。
错误2:误以为友元是双向的
class A {
friend void func(A&, B&); // func 可以访问 A 的私有成员
};
class B {
// ❌ func 不能访问 B 的私有成员,除非 B 也声明 func 为友元
};
✅ 正确做法:
class A {
friend void func(A&, B&);
};
class B {
friend void func(A&, B&); // 必须也声明
};
错误3:在友元声明中使用错误的参数类型
class Sales_data {
friend std::ostream& print(std::ostream&, Sales_data&); // ❌ 应该是 const 引用
};
✅ 正确做法:
friend std::ostream& print(std::ostream&, const Sales_data&); // ✅ 只读,安全高效
💡 实际应用示例
假设你有一个 Matrix 类,想重载 operator<< 输出矩阵:
class Matrix {
friend std::ostream& operator<<(std::ostream&, const Matrix&);
private:
std::vector<std::vector<int>> data;
};
std::ostream& operator<<(std::ostream& os, const Matrix& m) {
for (const auto& row : m.data) { // 访问私有成员 data
for (int val : row) {
os << val << " ";
}
os << "\n";
}
return os;
}
没有 friend,这段代码会编译失败!
📘 教学意义
本节内容是理解 C++ “封装”与“开放”平衡的关键:
- 封装性:通过
private保护内部实现。 - 灵活性:通过
friend有选择地开放接口。 - 设计哲学:不是所有相关操作都要写成成员函数;合理使用权责分离。
这也是后续学习“友元类”、“运算符重载”、“迭代器模式”等高级主题的基础。
✅ 总结对比表
| 特性 | 成员函数 | 非成员函数 + 友元 |
|---|---|---|
| 访问私有成员 | ✅ 自动拥有 | ✅ 需声明为 friend |
| 调用方式 | obj.func() | func(obj) |
| 对称性 | 左边必须是对象 | 参数可自由安排 |
| 封装性 | 强 | 较弱(需谨慎使用) |
| 适用场景 | 核心行为、状态修改 | IO、算术运算、工具函数 |
🎓 最终结论
“友元”是 C++ 中一种强大而危险的工具:
- 强大:它让你能在保持封装的同时,灵活扩展类的能力。
- 危险:滥用会破坏封装,导致代码难以维护。
✅ 最佳实践:
- 仅在必要时使用友元(如 IO、对称运算)。
- 尽量优先通过公有接口完成任务。
- 明确文档化每个友元的用途。
- 避免过度使用,尤其是跨多个类的复杂友元网络。
掌握友元机制,标志着你对 C++ 面向对象设计的理解进入了更深层次!
🎯 友元函数的具体应用场景
友元函数是 C++ 中一个特殊而强大的机制。下面我为您详细梳理友元函数的7大核心应用场景,每个场景都配有完整的代码示例。
📋 应用场景总览
| 场景 | 使用频率 | 必要性 | 典型例子 |
|---|---|---|---|
| 1️⃣ 输入输出运算符重载 | ⭐⭐⭐⭐⭐ | 必须 | operator<<, operator>> |
| 2️⃣ 对称二元运算符重载 | ⭐⭐⭐⭐⭐ | 必须 | operator+, operator== |
| 3️⃣ 访问多个类私有成员 | ⭐⭐⭐⭐ | 必须 | 跨类操作函数 |
| 4️⃣ 工厂函数 | ⭐⭐⭐ | 推荐 | 创建对象的辅助函数 |
| 5️⃣ 工具/辅助函数 | ⭐⭐⭐ | 推荐 | 数学计算、转换等 |
| 6️⃣ 迭代器模式 | ⭐⭐⭐ | 推荐 | 容器迭代器 |
| 7️⃣ 单例模式 | ⭐⭐ | 推荐 | 私有构造函数的类 |
1️⃣ 输入输出运算符重载(最经典场景)
🎯 为什么必须用友元?
operator<<的左操作数是ostream,不是我们的类- 无法将
operator<<定义为成员函数(成员函数的this指向左操作数) - 需要访问类的私有成员来输出
✅ 代码示例
#include <iostream>
#include <string>
class Person {
// 声明友元函数
friend std::ostream& operator<<(std::ostream&, const Person&);
friend std::istream& operator>>(std::istream&, Person&);
private:
std::string name;
int age;
std::string email; // 私有数据
public:
Person() = default;
Person(const std::string& n, int a, const std::string& e)
: name(n), age(a), email(e) {}
};
// 输出运算符重载(友元函数)
std::ostream& operator<<(std::ostream& os, const Person& p) {
os << "Name: " << p.name // ✅ 可以访问私有成员
<< ", Age: " << p.age
<< ", Email: " << p.email;
return os;
}
// 输入运算符重载(友元函数)
std::istream& operator>>(std::istream& is, Person& p) {
is >> p.name >> p.age >> p.email; // ✅ 可以访问私有成员
return is;
}
// 使用
int main() {
Person p("Alice", 25, "alice@example.com");
std::cout << p << std::endl; // 链式输出
Person p2;
std::cin >> p2; // 链式输入
std::cout << p2 << std::endl;
return 0;
}
📊 对比:成员函数 vs 友元函数
| 方式 | 代码 | 问题 |
|---|---|---|
| ❌ 成员函数 | p.operator<<(cout) | 语法反直觉,左操作数必须是对象 |
| ✅ 友元函数 | cout << p | 语法自然,符合习惯 |
2️⃣ 对称二元运算符重载
🎯 为什么必须用友元?
当运算符的两个操作数地位平等时(如 +, -, ==),如果定义为成员函数,左操作数必须是类对象,这会导致不对称:
// 成员函数版本
Complex c1, c2;
c1 + c2; // ✅ 可以:c1.operator+(c2)
5 + c2; // ❌ 错误:5 没有 operator+ 成员函数
✅ 代码示例
#include <iostream>
class Complex {
friend Complex operator+(const Complex&, const Complex&);
friend Complex operator+(const Complex&, double);
friend Complex operator+(double, const Complex&); // ✅ 关键!
private:
double real;
double imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
void print() const {
std::cout << real << " + " << imag << "i" << std::endl;
}
};
// 两个 Complex 相加
Complex operator+(const Complex& c1, const Complex& c2) {
return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
// Complex + double
Complex operator+(const Complex& c, double d) {
return Complex(c.real + d, c.imag);
}
// double + Complex(必须是友元!)
Complex operator+(double d, const Complex& c) {
return Complex(c.real + d, c.imag);
}
// 使用
int main() {
Complex c1(1, 2);
Complex c2(3, 4);
c1 + c2; // ✅ 两边都是 Complex
c1 + 5.0; // ✅ Complex + double
5.0 + c1; // ✅ double + Complex(成员函数无法实现!)
c1.print();
return 0;
}
📊 对称性对比
| 运算符 | 成员函数 | 友元函数 |
|---|---|---|
c1 + c2 | ✅ | ✅ |
c1 + 5.0 | ✅ | ✅ |
5.0 + c1 | ❌ | ✅ |
3️⃣ 访问多个类私有成员的函数
🎯 为什么必须用友元?
当一个函数需要同时访问两个或多个类的私有成员时,必须将这些类都声明该函数为友元。
✅ 代码示例
#include <iostream>
#include <string>
class Date; // 前向声明
class Person {
friend void compareAges(const Person&, const Person&);
friend void printPersonDate(const Person&, const Date&);
private:
std::string name;
int age;
public:
Person(const std::string& n, int a) : name(n), age(a) {}
std::string getName() const { return name; }
};
class Date {
friend void printPersonDate(const Person&, const Date&);
private:
int year, month, day;
public:
Date(int y, int m, int d) : year(y), month(m), day(d) {}
};
// 需要访问两个类的私有成员
void printPersonDate(const Person& p, const Date& d) {
std::cout << p.name << " was born on " // ✅ 访问 Person 私有成员
<< d.year << "-" << d.month << "-" << d.day // ✅ 访问 Date 私有成员
<< std::endl;
}
// 比较两个 Person 的年龄
void compareAges(const Person& p1, const Person& p2) {
if (p1.age > p2.age) // ✅ 访问私有成员
std::cout << p1.name << " is older" << std::endl;
else if (p1.age < p2.age)
std::cout << p2.name << " is older" << std::endl;
else
std::cout << "Same age" << std::endl;
}
// 使用
int main() {
Person p1("Alice", 25);
Person p2("Bob", 30);
Date d(1998, 5, 15);
compareAges(p1, p2);
printPersonDate(p1, d);
return 0;
}
4️⃣ 工厂函数(Factory Function)
🎯 为什么用友元?
当类有私有构造函数(如单例模式、对象池),但需要特定函数来创建对象时。
✅ 代码示例
#include <iostream>
#include <memory>
#include <vector>
class Database {
// 声明工厂函数为友元
friend std::unique_ptr<Database> createDatabase(const std::string& connStr);
friend class DatabasePool; // 友元类
private:
std::string connectionString;
bool connected;
// 私有构造函数:防止外部直接创建
Database(const std::string& conn)
: connectionString(conn), connected(false) {
std::cout << "Database created (private constructor)" << std::endl;
}
public:
void connect() {
connected = true;
std::cout << "Connected to " << connectionString << std::endl;
}
// 禁止拷贝
Database(const Database&) = delete;
Database& operator=(const Database&) = delete;
};
// 工厂函数:可以调用私有构造函数
std::unique_ptr<Database> createDatabase(const std::string& connStr) {
return std::make_unique<Database>(connStr); // ✅ 可以访问私有构造函数
}
// 对象池类(友元类)
class DatabasePool {
public:
std::vector<std::unique_ptr<Database>> pools;
void addDatabase(const std::string& connStr) {
pools.push_back(std::make_unique<Database>(connStr)); // ✅ 可以访问私有构造函数
}
};
// 使用
int main() {
// ❌ 错误:不能直接创建
// Database db("mysql://localhost");
// ✅ 正确:通过工厂函数创建
auto db = createDatabase("mysql://localhost");
db->connect();
// ✅ 正确:通过对象池创建
DatabasePool pool;
pool.addDatabase("mysql://db1");
pool.addDatabase("mysql://db2");
return 0;
}
5️⃣ 工具/辅助函数
🎯 为什么用友元?
当需要一个独立于类的工具函数,但又需要访问类的内部实现时。
✅ 代码示例
#include <iostream>
#include <cmath>
class Vector3D {
friend double dotProduct(const Vector3D&, const Vector3D&);
friend Vector3D crossProduct(const Vector3D&, const Vector3D&);
friend double distance(const Vector3D&, const Vector3D&);
private:
double x, y, z;
public:
Vector3D(double x = 0, double y = 0, double z = 0) : x(x), y(y), z(z) {}
double magnitude() const {
return std::sqrt(x*x + y*y + z*z);
}
};
// 点积(需要访问两个向量的私有成员)
double dotProduct(const Vector3D& v1, const Vector3D& v2) {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
// 叉积(返回新向量)
Vector3D crossProduct(const Vector3D& v1, const Vector3D& v2) {
return Vector3D(
v1.y * v2.z - v1.z * v2.y,
v1.z * v2.x - v1.x * v2.z,
v1.x * v2.y - v1.y * v2.x
);
}
// 计算两点距离
double distance(const Vector3D& v1, const Vector3D& v2) {
double dx = v1.x - v2.x;
double dy = v1.y - v2.y;
double dz = v1.z - v2.z;
return std::sqrt(dx*dx + dy*dy + dz*dz);
}
// 使用
int main() {
Vector3D v1(1, 2, 3);
Vector3D v2(4, 5, 6);
std::cout << "Dot product: " << dotProduct(v1, v2) << std::endl;
std::cout << "Distance: " << distance(v1, v2) << std::endl;
Vector3D v3 = crossProduct(v1, v2);
std::cout << "Cross product magnitude: " << v3.magnitude() << std::endl;
return 0;
}
6️⃣ 迭代器模式
🎯 为什么用友元?
迭代器需要访问容器的内部数据结构(如链表节点、数组指针),但不应该是容器的成员。
✅ 代码示例
#include <iostream>
class LinkedList; // 前向声明
class Iterator {
friend class LinkedList; // LinkedList 是 Iterator 的友元
private:
struct Node {
int data;
Node* next;
Node(int d) : data(d), next(nullptr) {}
};
Node* current;
// 私有构造函数:只能通过 LinkedList 创建
Iterator(Node* node) : current(node) {}
public:
int& operator*() { return current->data; }
Iterator& operator++() {
current = current->next;
return *this;
}
bool operator!=(const Iterator& other) const {
return current != other.current;
}
};
class LinkedList {
friend class Iterator; // Iterator 是 LinkedList 的友元
private:
struct Node {
int data;
Node* next;
Node(int d) : data(d), next(nullptr) {}
};
Node* head;
public:
LinkedList() : head(nullptr) {}
void pushFront(int value) {
Node* newNode = new Node(value);
newNode->next = head;
head = newNode;
}
// 返回迭代器(可以访问私有 Node 结构)
Iterator begin() {
return Iterator(head); // ✅ 可以访问 Iterator 的私有构造函数
}
Iterator end() {
return Iterator(nullptr);
}
};
// 使用
int main() {
LinkedList list;
list.pushFront(3);
list.pushFront(2);
list.pushFront(1);
for (Iterator it = list.begin(); it != list.end(); ++it) {
std::cout << *it << " "; // 输出: 1 2 3
}
std::cout << std::endl;
return 0;
}
7️⃣ 单例模式(Singleton)
🎯 为什么用友元?
单例模式需要私有构造函数防止外部创建,但需要特定方法获取唯一实例。
✅ 代码示例
#include <iostream>
class Singleton {
// 声明获取实例的函数为友元(可选,通常用静态成员函数)
friend Singleton& getInstance();
private:
static Singleton* instance;
int data;
// 私有构造函数
Singleton() : data(0) {
std::cout << "Singleton created" << std::endl;
}
// 禁止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
void setData(int d) { data = d; }
int getData() const { return data; }
// 析构函数
~Singleton() {
std::cout << "Singleton destroyed" << std::endl;
}
};
// 静态成员初始化
Singleton* Singleton::instance = nullptr;
// 友元函数:获取唯一实例
Singleton& getInstance() {
if (Singleton::instance == nullptr) {
Singleton::instance = new Singleton(); // ✅ 可以调用私有构造函数
}
return *Singleton::instance;
}
// 使用
int main() {
// ❌ 错误:不能直接创建
// Singleton s;
// ✅ 正确:通过友元函数获取
Singleton& s1 = getInstance();
s1.setData(42);
Singleton& s2 = getInstance(); // 返回同一个实例
std::cout << "Data: " << s2.getData() << std::endl;
std::cout << "Same instance: " << (&s1 == &s2) << std::endl;
return 0;
}
⚠️ 友元使用的注意事项
1. 友元破坏封装性
// ❌ 滥用友元:所有函数都是友元
class MyClass {
friend void func1(MyClass&);
friend void func2(MyClass&);
friend void func3(MyClass&);
// ... 太多友元 = 没有封装
private:
int data;
};
// ✅ 合理使用:仅必要的函数是友元
class MyClass {
friend std::ostream& operator<<(std::ostream&, const MyClass&);
private:
int data;
public:
int getData() const { return data; } // 通过公有接口访问
};
2. 友元不传递
class A {
friend void func(A&);
private:
int x;
};
class B {
// func 不是 B 的友元,即使 func 是 A 的友元
private:
int y;
};
void func(A& a) {
a.x = 10; // ✅ 可以
// b.y = 20; // ❌ 错误:func 不是 B 的友元
}
3. 友元不继承
class Base {
friend void func(Base&);
protected:
int x;
};
class Derived : public Base {
// func 不是 Derived 的友元!
private:
int y;
};
void func(Base& b) {
b.x = 10; // ✅ 可以访问 Base 的 protected 成员
// d.y = 20; // ❌ 错误:不能访问 Derived 的私有成员
}
📊 决策树:何时使用友元?
需要访问类的私有成员?
│
├─ 是 → 该函数应该是成员函数吗?
│ │
│ ├─ 是 → 定义为成员函数 ✅
│ │
│ └─ 否 → 需要访问多个类的私有成员吗?
│ │
│ ├─ 是 → 使用友元函数 ✅
│ │
│ └─ 否 → 左操作数必须是其他类型吗?(如 ostream)
│ │
│ ├─ 是 → 使用友元函数 ✅
│ │
│ └─ 否 → 考虑通过公有接口访问 ✅
│
└─ 否 → 不需要友元 ✅
✅ 最佳实践总结
| 原则 | 说明 |
|---|---|
| 最小化原则 | 只声明必要的友元,越少越好 |
| 文档化 | 注释说明为什么需要这个友元 |
| 优先公有接口 | 能通过公有成员函数解决的,不用友元 |
| 避免友元类 | 友元函数比友元类更安全(权限更小) |
| 谨慎跨模块 | 不同模块/库之间尽量避免友元 |
🎓 总结
友元函数是 C++ 中必要但需谨慎使用的机制:
| 场景 | 必要性 | 推荐度 |
|---|---|---|
| 输入输出运算符 | 必须 | ⭐⭐⭐⭐⭐ |
| 对称运算符 | 必须 | ⭐⭐⭐⭐⭐ |
| 跨类操作 | 必须 | ⭐⭐⭐⭐ |
| 工厂函数 | 推荐 | ⭐⭐⭐ |
| 工具函数 | 可选 | ⭐⭐ |
| 迭代器 | 推荐 | ⭐⭐⭐ |
| 单例模式 | 可选 | ⭐⭐ |
记住:友元是打破封装的"特权",应该像使用特权一样谨慎!
📘 友元函数完整指南:10+ 实际应用场景详解
我将为您提供一份超详细的友元函数应用指南,包含 10+ 个实际应用场景,每个都有完整的可运行代码示例。
📋 目录
| 场景 | 难度 | 实用性 | 章节 |
|---|---|---|---|
| 1. 输入输出运算符重载 | ⭐ | ⭐⭐⭐⭐⭐ | 点击查看 |
| 2. 对称二元运算符 | ⭐⭐ | ⭐⭐⭐⭐⭐ | 点击查看 |
| 3. 跨类私有成员访问 | ⭐⭐⭐ | ⭐⭐⭐⭐ | 点击查看 |
| 4. 工厂模式 | ⭐⭐⭐ | ⭐⭐⭐⭐ | 点击查看 |
| 5. 单例模式 | ⭐⭐ | ⭐⭐⭐⭐ | 点击查看 |
| 6. 迭代器模式 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 点击查看 |
| 7. 矩阵/向量运算 | ⭐⭐⭐ | ⭐⭐⭐⭐ | 点击查看 |
| 8. 序列化/反序列化 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 点击查看 |
| 9. 测试框架 | ⭐⭐⭐ | ⭐⭐⭐⭐ | 点击查看 |
| 10. 缓存系统 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 点击查看 |
| 11. 观察者模式 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 点击查看 |
| 12. 智能指针 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 点击查看 |
场景1:输入输出运算符重载
🎯 为什么必须用友元?
operator<< 的左操作数是 ostream,不是我们的类,无法定义为成员函数。
✅ 完整代码示例
// File: student.h
#ifndef STUDENT_H
#define STUDENT_H
#include <iostream>
#include <string>
#include <vector>
class Student {
// 友元声明:输入输出运算符
friend std::ostream& operator<<(std::ostream&, const Student&);
friend std::istream& operator>>(std::istream&, Student&);
private:
std::string name;
int id;
double gpa;
std::vector<std::string> courses;
public:
Student() : id(0), gpa(0.0) {}
Student(const std::string& n, int i, double g)
: name(n), id(i), gpa(g) {}
void addCourse(const std::string& course) {
courses.push_back(course);
}
std::string getName() const { return name; }
int getId() const { return id; }
double getGPA() const { return gpa; }
};
// 输出运算符重载
std::ostream& operator<<(std::ostream& os, const Student& s) {
os << "Student[ID: " << s.id << ", Name: " << s.name
<< ", GPA: " << s.gpa << ", Courses: ";
for (size_t i = 0; i < s.courses.size(); ++i) {
if (i > 0) os << ", ";
os << s.courses[i];
}
os << "]";
return os;
}
// 输入运算符重载
std::istream& operator>>(std::istream& is, Student& s) {
std::cout << "Enter name: ";
is >> s.name;
std::cout << "Enter ID: ";
is >> s.id;
std::cout << "Enter GPA: ";
is >> s.gpa;
return is;
}
#endif
// File: main.cpp
#include "student.h"
int main() {
Student s1("Alice", 1001, 3.8);
s1.addCourse("Math");
s1.addCourse("Physics");
// 链式输出
std::cout << s1 << std::endl;
// 链式输入
Student s2;
std::cin >> s2;
std::cout << s2 << std::endl;
// 输出到文件
#include <fstream>
std::ofstream file("student.txt");
file << s1 << std::endl;
file.close();
return 0;
}
📊 对比:成员函数 vs 友元函数
| 特性 | 成员函数(不可行) | 友元函数(推荐) |
|---|---|---|
| 语法 | s.operator<<(cout) | cout << s |
| 链式调用 | ❌ 困难 | ✅ 自然 |
| 左操作数 | 必须是 Student | 可以是 ostream |
| 符合习惯 | ❌ 反直觉 | ✅ 符合直觉 |
场景2:对称二元运算符
🎯 为什么必须用友元?
确保 a + b 和 b + a 都能工作,即使其中一个不是类对象。
✅ 完整代码示例
// File: rational.h
#ifndef RATIONAL_H
#define RATIONAL_H
#include <iostream>
#include <numeric>
class Rational {
friend Rational operator+(const Rational&, const Rational&);
friend Rational operator+(const Rational&, int);
friend Rational operator+(int, const Rational&); // 关键!
friend Rational operator*(const Rational&, const Rational&);
friend Rational operator*(const Rational&, int);
friend Rational operator*(int, const Rational&);
friend bool operator==(const Rational&, const Rational&);
friend bool operator==(const Rational&, int);
friend bool operator==(int, const Rational&);
friend std::ostream& operator<<(std::ostream&, const Rational&);
private:
int numerator;
int denominator;
void simplify() {
int gcd = std::gcd(numerator, denominator);
numerator /= gcd;
denominator /= gcd;
if (denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
}
public:
Rational(int n = 0, int d = 1) : numerator(n), denominator(d) {
simplify();
}
double toDouble() const {
return static_cast<double>(numerator) / denominator;
}
};
// Rational + Rational
Rational operator+(const Rational& r1, const Rational& r2) {
int n = r1.numerator * r2.denominator + r2.numerator * r1.denominator;
int d = r1.denominator * r2.denominator;
return Rational(n, d);
}
// Rational + int
Rational operator+(const Rational& r, int i) {
return r + Rational(i, 1);
}
// int + Rational(必须是友元!)
Rational operator+(int i, const Rational& r) {
return Rational(i, 1) + r;
}
// Rational * Rational
Rational operator*(const Rational& r1, const Rational& r2) {
return Rational(r1.numerator * r2.numerator,
r1.denominator * r2.denominator);
}
// Rational * int
Rational operator*(const Rational& r, int i) {
return r * Rational(i, 1);
}
// int * Rational
Rational operator*(int i, const Rational& r) {
return Rational(i, 1) * r;
}
// 比较运算符
bool operator==(const Rational& r1, const Rational& r2) {
return r1.numerator == r2.numerator &&
r1.denominator == r2.denominator;
}
bool operator==(const Rational& r, int i) {
return r == Rational(i, 1);
}
bool operator==(int i, const Rational& r) {
return Rational(i, 1) == r;
}
// 输出
std::ostream& operator<<(std::ostream& os, const Rational& r) {
if (r.denominator == 1)
os << r.numerator;
else
os << r.numerator << "/" << r.denominator;
return os;
}
#endif
// File: main.cpp
#include "rational.h"
int main() {
Rational r1(1, 2); // 1/2
Rational r2(1, 3); // 1/3
// 对称性测试
std::cout << r1 << " + " << r2 << " = " << (r1 + r2) << std::endl;
std::cout << r1 << " + " << 5 << " = " << (r1 + 5) << std::endl;
std::cout << 5 << " + " << r1 << " = " << (5 + r1) << std::endl; // 关键!
std::cout << r1 << " * " << r2 << " = " << (r1 * r2) << std::endl;
std::cout << r1 << " * " << 3 << " = " << (r1 * 3) << std::endl;
std::cout << 3 << " * " << r1 << " = " << (3 * r1) << std::endl; // 关键!
// 比较
std::cout << (r1 == Rational(1, 2)) << std::endl; // 1
std::cout << (r1 == 0) << std::endl; // 0
std::cout << (1 == Rational(1, 1)) << std::endl; // 1
return 0;
}
📊 对称性测试结果
1/2 + 1/3 = 5/6
1/2 + 5 = 11/2
5 + 1/2 = 11/2 ← 成员函数无法实现!
1/2 * 1/3 = 1/6
1/2 * 3 = 3/2
3 * 1/2 = 3/2 ← 成员函数无法实现!
场景3:跨类私有成员访问
🎯 为什么必须用友元?
一个函数需要同时访问两个不同类的私有成员。
✅ 完整代码示例
// File: geometry.h
#ifndef GEOMETRY_H
#define GEOMETRY_H
#include <iostream>
#include <cmath>
#include <string>
class Point;
class Rectangle;
// 跨类操作函数声明
double distance(const Point&, const Point&);
bool contains(const Rectangle&, const Point&);
void printRelation(const Rectangle&, const Point&);
class Point {
friend double distance(const Point&, const Point&);
friend bool contains(const Rectangle&, const Point&);
friend void printRelation(const Rectangle&, const Point&);
private:
double x, y;
public:
Point(double x = 0, double y = 0) : x(x), y(y) {}
std::string toString() const {
return "(" + std::to_string(x) + ", " + std::to_string(y) + ")";
}
};
class Rectangle {
friend bool contains(const Rectangle&, const Point&);
friend void printRelation(const Rectangle&, const Point&);
private:
Point topLeft;
Point bottomRight;
public:
Rectangle(double x1, double y1, double x2, double y2)
: topLeft(x1, y1), bottomRight(x2, y2) {}
double getWidth() const {
return bottomRight.x - topLeft.x;
}
double getHeight() const {
return topLeft.y - bottomRight.y;
}
double getArea() const {
return getWidth() * getHeight();
}
};
// 计算两点距离(访问两个 Point 的私有成员)
double distance(const Point& p1, const Point& p2) {
double dx = p1.x - p2.x; // ✅ 访问私有成员
double dy = p1.y - p2.y; // ✅ 访问私有成员
return std::sqrt(dx * dx + dy * dy);
}
// 判断点是否在矩形内(访问 Rectangle 和 Point 的私有成员)
bool contains(const Rectangle& rect, const Point& p) {
return p.x >= rect.topLeft.x && p.x <= rect.bottomRight.x &&
p.y <= rect.topLeft.y && p.y >= rect.bottomRight.y;
}
// 打印关系信息
void printRelation(const Rectangle& rect, const Point& p) {
std::cout << "Rectangle area: " << rect.getArea() << std::endl;
std::cout << "Point: " << p.toString() << std::endl;
if (contains(rect, p)) {
std::cout << "Point is INSIDE the rectangle" << std::endl;
} else {
std::cout << "Point is OUTSIDE the rectangle" << std::endl;
}
double distToTopLeft = distance(p, rect.topLeft);
std::cout << "Distance to top-left: " << distToTopLeft << std::endl;
}
#endif
// File: main.cpp
#include "geometry.h"
int main() {
Point p1(0, 0);
Point p2(3, 4);
std::cout << "Distance between " << p1.toString()
<< " and " << p2.toString()
<< " = " << distance(p1, p2) << std::endl;
Rectangle rect(0, 10, 10, 0);
Point p3(5, 5);
Point p4(15, 15);
std::cout << "\n--- Rectangle Test ---" << std::endl;
printRelation(rect, p3);
std::cout << std::endl;
printRelation(rect, p4);
return 0;
}
📊 输出结果
Distance between (0.000000, 0.000000) and (3.000000, 4.000000) = 5
--- Rectangle Test ---
Rectangle area: 100
Point: (5.000000, 5.000000)
Point is INSIDE the rectangle
Distance to top-left: 7.07107
Rectangle area: 100
Point: (15.000000, 15.000000)
Point is OUTSIDE the rectangle
Distance to top-left: 18.0278
场景4:工厂模式
🎯 为什么用友元?
控制对象创建,确保所有对象都通过工厂函数创建(便于资源管理、日志记录等)。
✅ 完整代码示例
// File: vehicle.h
#ifndef VEHICLE_H
#define VEHICLE_H
#include <iostream>
#include <string>
#include <memory>
#include <vector>
class VehicleFactory;
class Vehicle {
friend class VehicleFactory;
private:
std::string type;
std::string model;
int year;
double price;
// 私有构造函数
Vehicle(const std::string& t, const std::string& m, int y, double p)
: type(t), model(m), year(y), price(p) {
std::cout << "[LOG] Vehicle created: " << model << std::endl;
}
public:
// 禁止拷贝
Vehicle(const Vehicle&) = delete;
Vehicle& operator=(const Vehicle&) = delete;
// 允许移动
Vehicle(Vehicle&&) = default;
Vehicle& operator=(Vehicle&&) = default;
std::string getInfo() const {
return type + " - " + model + " (" + std::to_string(year) + ") - $"
+ std::to_string(static_cast<int>(price));
}
~Vehicle() {
std::cout << "[LOG] Vehicle destroyed: " << model << std::endl;
}
};
// 工厂类
class VehicleFactory {
private:
static std::vector<std::unique_ptr<Vehicle>> inventory;
public:
// 创建车辆(可以调用私有构造函数)
static std::unique_ptr<Vehicle> createCar(const std::string& model,
int year, double price) {
auto v = std::make_unique<Vehicle>("Car", model, year, price);
inventory.push_back(std::make_unique<Vehicle>(*v));
return v;
}
static std::unique_ptr<Vehicle> createTruck(const std::string& model,
int year, double price) {
auto v = std::make_unique<Vehicle>("Truck", model, year, price);
inventory.push_back(std::make_unique<Vehicle>(*v));
return v;
}
static size_t getInventorySize() {
return inventory.size();
}
static void printInventory() {
std::cout << "\n=== Vehicle Inventory ===" << std::endl;
for (const auto& v : inventory) {
std::cout << v->getInfo() << std::endl;
}
std::cout << "Total: " << inventory.size() << " vehicles" << std::endl;
}
};
std::vector<std::unique_ptr<Vehicle>> VehicleFactory::inventory;
#endif
// File: main.cpp
#include "vehicle.h"
int main() {
// ❌ 错误:不能直接创建
// Vehicle v("Car", "Toyota", 2023, 25000);
// ✅ 正确:通过工厂创建
auto car1 = VehicleFactory::createCar("Toyota Camry", 2023, 25000);
auto car2 = VehicleFactory::createCar("Honda Accord", 2022, 24000);
auto truck1 = VehicleFactory::createTruck("Ford F-150", 2023, 35000);
std::cout << "\nCreated vehicles:" << std::endl;
std::cout << car1->getInfo() << std::endl;
std::cout << car2->getInfo() << std::endl;
std::cout << truck1->getInfo() << std::endl;
VehicleFactory::printInventory();
return 0;
}
📊 输出结果
[LOG] Vehicle created: Toyota Camry
[LOG] Vehicle created: Honda Accord
[LOG] Vehicle created: Ford F-150
Created vehicles:
Car - Toyota Camry (2023) - $25000
Car - Honda Accord (2022) - $24000
Truck - Ford F-150 (2023) - $35000
=== Vehicle Inventory ===
Car - Toyota Camry (2023) - $25000
Car - Honda Accord (2022) - $24000
Truck - Ford F-150 (2023) - $35000
Total: 3 vehicles
[LOG] Vehicle destroyed: Ford F-150
[LOG] Vehicle destroyed: Honda Accord
[LOG] Vehicle destroyed: Toyota Camry
场景5:单例模式
🎯 为什么用友元?
确保只有一个实例,防止外部直接创建对象。
✅ 完整代码示例
// File: logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <iostream>
#include <fstream>
#include <string>
#include <mutex>
#include <vector>
class Logger {
// 友元函数:获取唯一实例
friend Logger& getLogger();
friend void destroyLogger();
private:
static Logger* instance;
static std::mutex mutex;
std::string logFile;
std::vector<std::string> logBuffer;
bool fileEnabled;
// 私有构造函数
Logger() : logFile("app.log"), fileEnabled(true) {
std::cout << "[Logger] Initialized" << std::endl;
}
// 禁止拷贝和赋值
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
void setLogFile(const std::string& file) {
logFile = file;
}
void enableFileLogging(bool enable) {
fileEnabled = enable;
}
void log(const std::string& message) {
std::lock_guard<std::mutex> lock(mutex);
std::string logEntry = "[LOG] " + message;
logBuffer.push_back(logEntry);
std::cout << logEntry << std::endl;
if (fileEnabled) {
std::ofstream ofs(logFile, std::ios::app);
if (ofs.is_open()) {
ofs << logEntry << std::endl;
ofs.close();
}
}
}
void logError(const std::string& message) {
log("[ERROR] " + message);
}
void logWarning(const std::string& message) {
log("[WARNING] " + message);
}
void printHistory() const {
std::cout << "\n=== Log History ===" << std::endl;
for (const auto& entry : logBuffer) {
std::cout << entry << std::endl;
}
}
~Logger() {
std::cout << "[Logger] Destroyed" << std::endl;
}
};
// 静态成员初始化
Logger* Logger::instance = nullptr;
std::mutex Logger::mutex;
// 获取单例实例
Logger& getLogger() {
std::lock_guard<std::mutex> lock(Logger::mutex);
if (Logger::instance == nullptr) {
Logger::instance = new Logger();
}
return *Logger::instance;
}
// 销毁单例
void destroyLogger() {
std::lock_guard<std::mutex> lock(Logger::mutex);
delete Logger::instance;
Logger::instance = nullptr;
}
#endif
// File: main.cpp
#include "logger.h"
void someFunction() {
Logger& logger = getLogger();
logger.log("Entering someFunction");
// ... 做一些事情
logger.log("Exiting someFunction");
}
int main() {
Logger& logger = getLogger();
logger.setLogFile("myapp.log");
logger.log("Application started");
logger.logWarning("Low memory warning");
someFunction();
logger.logError("Something went wrong!");
logger.log("Application ending");
logger.printHistory();
destroyLogger();
return 0;
}
📊 输出结果
[Logger] Initialized
[LOG] Application started
[LOG] [WARNING] Low memory warning
[LOG] Entering someFunction
[LOG] Exiting someFunction
[LOG] [ERROR] Something went wrong!
[LOG] Application ending
=== Log History ===
[LOG] Application started
[LOG] [WARNING] Low memory warning
[LOG] Entering someFunction
[LOG] Exiting someFunction
[LOG] [ERROR] Something went wrong!
[LOG] Application ending
[Logger] Destroyed
场景6:迭代器模式
🎯 为什么用友元?
迭代器需要访问容器的内部数据结构,但不应该是容器的成员。
✅ 完整代码示例
// File: simple_vector.h
#ifndef SIMPLE_VECTOR_H
#define SIMPLE_VECTOR_H
#include <iostream>
#include <memory>
#include <initializer_list>
template<typename T>
class SimpleVector;
template<typename T>
class VectorIterator {
friend class SimpleVector<T>;
private:
T* ptr;
// 私有构造函数
VectorIterator(T* p) : ptr(p) {}
public:
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
VectorIterator& operator++() {
++ptr;
return *this;
}
VectorIterator operator++(int) {
VectorIterator tmp = *this;
++ptr;
return tmp;
}
VectorIterator& operator--() {
--ptr;
return *this;
}
bool operator==(const VectorIterator& other) const {
return ptr == other.ptr;
}
bool operator!=(const VectorIterator& other) const {
return ptr != other.ptr;
}
};
template<typename T>
class SimpleVector {
friend class VectorIterator<T>;
private:
T* data;
size_t size;
size_t capacity;
void resize(size_t newCapacity) {
T* newData = new T[newCapacity];
for (size_t i = 0; i < size; ++i) {
newData[i] = data[i];
}
delete[] data;
data = newData;
capacity = newCapacity;
}
public:
SimpleVector() : data(nullptr), size(0), capacity(0) {}
SimpleVector(std::initializer_list<T> init)
: data(new T[init.size()]), size(init.size()), capacity(init.size()) {
size_t i = 0;
for (const auto& elem : init) {
data[i++] = elem;
}
}
~SimpleVector() {
delete[] data;
}
// 禁止拷贝(简化示例)
SimpleVector(const SimpleVector&) = delete;
SimpleVector& operator=(const SimpleVector&) = delete;
void pushBack(const T& value) {
if (size >= capacity) {
resize(capacity == 0 ? 4 : capacity * 2);
}
data[size++] = value;
}
T& operator[](size_t index) {
return data[index];
}
const T& operator[](size_t index) const {
return data[index];
}
size_t getSize() const { return size; }
bool empty() const { return size == 0; }
// 返回迭代器
VectorIterator<T> begin() {
return VectorIterator<T>(data);
}
VectorIterator<T> end() {
return VectorIterator<T>(data + size);
}
VectorIterator<T> begin() const {
return VectorIterator<T>(data);
}
VectorIterator<T> end() const {
return VectorIterator<T>(data + size);
}
};
#endif
// File: main.cpp
#include "simple_vector.h"
int main() {
SimpleVector<int> vec = {1, 2, 3, 4, 5};
// 范围 for 循环
std::cout << "Range-based for: ";
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
// 迭代器
std::cout << "Iterator: ";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 修改元素
for (auto it = vec.begin(); it != vec.end(); ++it) {
*it *= 2;
}
std::cout << "After doubling: ";
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
// 添加元素
vec.pushBack(6);
vec.pushBack(7);
std::cout << "After adding: ";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
📊 输出结果
Range-based for: 1 2 3 4 5
Iterator: 1 2 3 4 5
After doubling: 2 4 6 8 10
After adding: 2 4 6 8 10 12 14
场景7:矩阵/向量运算
🎯 为什么用友元?
矩阵运算需要访问多个矩阵的私有数据,且运算符需要对称性。
✅ 完整代码示例
// File: matrix.h
#ifndef MATRIX_H
#define MATRIX_H
#include <iostream>
#include <vector>
#include <stdexcept>
class Matrix {
friend Matrix operator+(const Matrix&, const Matrix&);
friend Matrix operator-(const Matrix&, const Matrix&);
friend Matrix operator*(const Matrix&, const Matrix&);
friend Matrix operator*(double, const Matrix&);
friend Matrix operator*(const Matrix&, double);
friend std::ostream& operator<<(std::ostream&, const Matrix&);
private:
std::vector<std::vector<double>> data;
size_t rows;
size_t cols;
public:
Matrix(size_t r, size_t c) : rows(r), cols(c) {
data.resize(rows, std::vector<double>(cols, 0.0));
}
Matrix(std::initializer_list<std::initializer_list<double>> init) {
rows = init.size();
cols = rows > 0 ? init.begin()->size() : 0;
data.resize(rows);
size_t i = 0;
for (const auto& row : init) {
data[i].assign(row.begin(), row.end());
++i;
}
}
double& at(size_t r, size_t c) {
if (r >= rows || c >= cols) throw std::out_of_range("Index out of bounds");
return data[r][c];
}
double at(size_t r, size_t c) const {
if (r >= rows || c >= cols) throw std::out_of_range("Index out of bounds");
return data[r][c];
}
size_t getRows() const { return rows; }
size_t getCols() const { return cols; }
Matrix transpose() const {
Matrix result(cols, rows);
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < cols; ++j) {
result.data[j][i] = data[i][j];
}
}
return result;
}
};
// 矩阵加法
Matrix operator+(const Matrix& a, const Matrix& b) {
if (a.rows != b.rows || a.cols != b.cols) {
throw std::invalid_argument("Matrix dimensions must match for addition");
}
Matrix result(a.rows, a.cols);
for (size_t i = 0; i < a.rows; ++i) {
for (size_t j = 0; j < a.cols; ++j) {
result.data[i][j] = a.data[i][j] + b.data[i][j];
}
}
return result;
}
// 矩阵减法
Matrix operator-(const Matrix& a, const Matrix& b) {
if (a.rows != b.rows || a.cols != b.cols) {
throw std::invalid_argument("Matrix dimensions must match for subtraction");
}
Matrix result(a.rows, a.cols);
for (size_t i = 0; i < a.rows; ++i) {
for (size_t j = 0; j < a.cols; ++j) {
result.data[i][j] = a.data[i][j] - b.data[i][j];
}
}
return result;
}
// 矩阵乘法
Matrix operator*(const Matrix& a, const Matrix& b) {
if (a.cols != b.rows) {
throw std::invalid_argument("Matrix dimensions incompatible for multiplication");
}
Matrix result(a.rows, b.cols);
for (size_t i = 0; i < a.rows; ++i) {
for (size_t j = 0; j < b.cols; ++j) {
for (size_t k = 0; k < a.cols; ++k) {
result.data[i][j] += a.data[i][k] * b.data[k][j];
}
}
}
return result;
}
// 标量 * 矩阵
Matrix operator*(double scalar, const Matrix& m) {
Matrix result(m.rows, m.cols);
for (size_t i = 0; i < m.rows; ++i) {
for (size_t j = 0; j < m.cols; ++j) {
result.data[i][j] = scalar * m.data[i][j];
}
}
return result;
}
// 矩阵 * 标量
Matrix operator*(const Matrix& m, double scalar) {
return scalar * m;
}
// 输出
std::ostream& operator<<(std::ostream& os, const Matrix& m) {
for (size_t i = 0; i < m.rows; ++i) {
os << "| ";
for (size_t j = 0; j < m.cols; ++j) {
os << m.data[i][j] << " ";
}
os << "|" << std::endl;
}
return os;
}
#endif
// File: main.cpp
#include "matrix.h"
int main() {
Matrix a = {{1, 2}, {3, 4}};
Matrix b = {{5, 6}, {7, 8}};
std::cout << "Matrix A:" << std::endl << a << std::endl;
std::cout << "Matrix B:" << std::endl << b << std::endl;
std::cout << "A + B:" << std::endl << (a + b) << std::endl;
std::cout << "A - B:" << std::endl << (a - b) << std::endl;
std::cout << "A * B:" << std::endl << (a * b) << std::endl;
std::cout << "2 * A:" << std::endl << (2 * a) << std::endl;
std::cout << "A * 2:" << std::endl << (a * 2) << std::endl;
std::cout << "A transpose:" << std::endl << a.transpose() << std::endl;
return 0;
}
📊 输出结果
Matrix A:
| 1 2 |
| 3 4 |
Matrix B:
| 5 6 |
| 7 8 |
A + B:
| 6 8 |
| 10 12 |
A - B:
| -4 -4 |
| -4 -4 |
A * B:
| 19 22 |
| 43 50 |
2 * A:
| 2 4 |
| 6 8 |
A * 2:
| 2 4 |
| 6 8 |
A transpose:
| 1 3 |
| 2 4 |
场景8:序列化/反序列化
🎯 为什么用友元?
序列化需要访问对象的所有私有数据,但不应该是类的成员函数(便于扩展不同格式)。
✅ 完整代码示例
// File: serializable.h
#ifndef SERIALIZABLE_H
#define SERIALIZABLE_H
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
class Person;
// 序列化函数声明
std::string serialize(const Person& p);
Person deserialize(const std::string& data);
bool saveToFile(const Person& p, const std::string& filename);
Person loadFromFile(const std::string& filename);
class Person {
friend std::string serialize(const Person& p);
friend Person deserialize(const std::string& data);
friend bool saveToFile(const Person& p, const std::string& filename);
friend Person loadFromFile(const std::string& filename);
private:
std::string name;
int age;
std::string email;
std::vector<std::string> hobbies;
public:
Person() : age(0) {}
Person(const std::string& n, int a, const std::string& e)
: name(n), age(a), email(e) {}
void addHobby(const std::string& hobby) {
hobbies.push_back(hobby);
}
void print() const {
std::cout << "Name: " << name << ", Age: " << age
<< ", Email: " << email << std::endl;
std::cout << "Hobbies: ";
for (const auto& h : hobbies) {
std::cout << h << " ";
}
std::cout << std::endl;
}
};
// JSON 格式序列化
std::string serialize(const Person& p) {
std::ostringstream oss;
oss << "{\"name\":\"" << p.name << "\","
<< "\"age\":" << p.age << ","
<< "\"email\":\"" << p.email << "\","
<< "\"hobbies\":[";
for (size_t i = 0; i < p.hobbies.size(); ++i) {
if (i > 0) oss << ",";
oss << "\"" << p.hobbies[i] << "\"";
}
oss << "]}";
return oss.str();
}
// JSON 格式反序列化(简化版)
Person deserialize(const std::string& data) {
Person p;
// 简化解析(实际项目应使用 JSON 库)
size_t nameStart = data.find("\"name\":\"") + 8;
size_t nameEnd = data.find("\"", nameStart);
p.name = data.substr(nameStart, nameEnd - nameStart);
size_t ageStart = data.find("\"age\":") + 6;
size_t ageEnd = data.find(",", ageStart);
p.age = std::stoi(data.substr(ageStart, ageEnd - ageStart));
size_t emailStart = data.find("\"email\":\"") + 9;
size_t emailEnd = data.find("\"", emailStart);
p.email = data.substr(emailStart, emailEnd - emailStart);
return p;
}
// 保存到文件
bool saveToFile(const Person& p, const std::string& filename) {
std::ofstream file(filename);
if (!file.is_open()) return false;
file << serialize(p);
file.close();
return true;
}
// 从文件加载
Person loadFromFile(const std::string& filename) {
std::ifstream file(filename);
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
return deserialize(content);
}
#endif
// File: main.cpp
#include "serializable.h"
int main() {
Person p1("Alice", 25, "alice@example.com");
p1.addHobby("Reading");
p1.addHobby("Coding");
p1.addHobby("Hiking");
std::cout << "Original Person:" << std::endl;
p1.print();
// 序列化
std::string json = serialize(p1);
std::cout << "\nSerialized JSON:" << std::endl;
std::cout << json << std::endl;
// 保存到文件
if (saveToFile(p1, "person.json")) {
std::cout << "\nSaved to person.json" << std::endl;
}
// 从文件加载
Person p2 = loadFromFile("person.json");
std::cout << "\nLoaded Person:" << std::endl;
p2.print();
// 反序列化
Person p3 = deserialize(json);
std::cout << "\nDeserialized Person:" << std::endl;
p3.print();
return 0;
}
📊 输出结果
Original Person:
Name: Alice, Age: 25, Email: alice@example.com
Hobbies: Reading Coding Hiking
Serialized JSON:
{"name":"Alice","age":25,"email":"alice@example.com","hobbies":["Reading","Coding","Hiking"]}
Saved to person.json
Loaded Person:
Name: Alice, Age: 25, Email: alice@example.com
Hobbies:
Deserialized Person:
Name: Alice, Age: 25, Email: alice@example.com
Hobbies:
场景9:测试框架
🎯 为什么用友元?
测试代码需要访问被测试类的私有成员进行验证,但不应该修改生产代码的访问权限。
✅ 完整代码示例
// File: calculator.h
#ifndef CALCULATOR_H
#define CALCULATOR_H
#include <iostream>
#include <vector>
class CalculatorTest;
class Calculator {
friend class CalculatorTest;
private:
double memory;
std::vector<double> history;
int operationCount;
double internalCalculate(double a, double b, char op) {
double result = 0;
switch (op) {
case '+': result = a + b; break;
case '-': result = a - b; break;
case '*': result = a * b; break;
case '/': result = (b != 0) ? a / b : 0; break;
}
return result;
}
public:
Calculator() : memory(0), operationCount(0) {}
double add(double a, double b) {
double result = internalCalculate(a, b, '+');
history.push_back(result);
operationCount++;
return result;
}
double subtract(double a, double b) {
double result = internalCalculate(a, b, '-');
history.push_back(result);
operationCount++;
return result;
}
double multiply(double a, double b) {
double result = internalCalculate(a, b, '*');
history.push_back(result);
operationCount++;
return result;
}
double divide(double a, double b) {
double result = internalCalculate(a, b, '/');
history.push_back(result);
operationCount++;
return result;
}
void storeToMemory(double value) {
memory = value;
}
double recallFromMemory() const {
return memory;
}
int getOperationCount() const {
return operationCount;
}
};
// 测试类
class CalculatorTest {
public:
static void testInternalCalculate() {
Calculator calc;
std::cout << "Testing internalCalculate..." << std::endl;
double result = calc.internalCalculate(10, 5, '+');
std::cout << "10 + 5 = " << result << " (expected: 15)" << std::endl;
result = calc.internalCalculate(10, 5, '-');
std::cout << "10 - 5 = " << result << " (expected: 5)" << std::endl;
result = calc.internalCalculate(10, 5, '*');
std::cout << "10 * 5 = " << result << " (expected: 50)" << std::endl;
result = calc.internalCalculate(10, 5, '/');
std::cout << "10 / 5 = " << result << " (expected: 2)" << std::endl;
}
static void testPrivateMembers() {
Calculator calc;
calc.add(1, 2);
calc.add(3, 4);
calc.multiply(2, 3);
std::cout << "\nTesting private members access..." << std::endl;
std::cout << "Operation count: " << calc.operationCount << " (expected: 3)" << std::endl;
std::cout << "History size: " << calc.history.size() << " (expected: 3)" << std::endl;
std::cout << "Memory value: " << calc.memory << " (expected: 0)" << std::endl;
calc.storeToMemory(100);
std::cout << "After storing 100, memory: " << calc.memory << " (expected: 100)" << std::endl;
}
static void runAllTests() {
std::cout << "=== Calculator Test Suite ===" << std::endl;
testInternalCalculate();
testPrivateMembers();
std::cout << "\n=== All Tests Completed ===" << std::endl;
}
};
#endif
// File: main.cpp
#include "calculator.h"
int main() {
// 运行测试
CalculatorTest::runAllTests();
// 正常使用
std::cout << "\n=== Normal Usage ===" << std::endl;
Calculator calc;
std::cout << "5 + 3 = " << calc.add(5, 3) << std::endl;
std::cout << "10 - 4 = " << calc.subtract(10, 4) << std::endl;
std::cout << "6 * 7 = " << calc.multiply(6, 7) << std::endl;
std::cout << "20 / 4 = " << calc.divide(20, 4) << std::endl;
return 0;
}
📊 输出结果
=== Calculator Test Suite ===
Testing internalCalculate...
10 + 5 = 15 (expected: 15)
10 - 5 = 5 (expected: 5)
10 * 5 = 50 (expected: 50)
10 / 5 = 2 (expected: 2)
Testing private members access...
Operation count: 3 (expected: 3)
History size: 3 (expected: 3)
Memory value: 0 (expected: 0)
After storing 100, memory: 100 (expected: 100)
=== All Tests Completed ===
=== Normal Usage ===
5 + 3 = 8
10 - 4 = 6
6 * 7 = 42
20 / 4 = 5
场景10:缓存系统
🎯 为什么用友元?
缓存管理器需要访问被缓存对象的内部状态来判断是否有效、是否需要更新。
✅ 完整代码示例
// File: cache.h
#ifndef CACHE_H
#define CACHE_H
#include <iostream>
#include <string>
#include <unordered_map>
#include <chrono>
#include <memory>
template<typename T>
class CacheManager;
template<typename T>
class CachedObject {
friend class CacheManager<T>;
private:
T data;
std::chrono::system_clock::time_point createdAt;
std::chrono::system_clock::time_point lastAccessed;
int accessCount;
bool isValid;
std::string key;
CachedObject(const std::string& k, const T& d)
: data(d), key(k), accessCount(0), isValid(true) {
createdAt = lastAccessed = std::chrono::system_clock::now();
}
void markAccessed() {
lastAccessed = std::chrono::system_clock::now();
accessCount++;
}
void invalidate() {
isValid = false;
}
public:
const T& getData() const { return data; }
std::string getKey() const { return key; }
int getAccessCount() const { return accessCount; }
std::chrono::seconds getAge() const {
auto now = std::chrono::system_clock::now();
return std::chrono::duration_cast<std::chrono::seconds>(now - createdAt);
}
};
template<typename T>
class CacheManager {
private:
std::unordered_map<std::string, std::shared_ptr<CachedObject<T>>> cache;
size_t maxSize;
std::chrono::seconds maxAge;
void evictIfNeeded() {
if (cache.size() <= maxSize) return;
// 简单的 LRU 策略:删除最久未访问的
std::string oldestKey;
auto oldestTime = std::chrono::system_clock::now();
for (const auto& pair : cache) {
if (pair.second->lastAccessed < oldestTime) {
oldestTime = pair.second->lastAccessed;
oldestKey = pair.first;
}
}
if (!oldestKey.empty()) {
std::cout << "[Cache] Evicting: " << oldestKey << std::endl;
cache.erase(oldestKey);
}
}
void removeExpired() {
auto now = std::chrono::system_clock::now();
for (auto it = cache.begin(); it != cache.end(); ) {
auto age = std::chrono::duration_cast<std::chrono::seconds>(
now - it->second->createdAt);
if (age > maxAge) {
std::cout << "[Cache] Expired: " << it->first << std::endl;
it = cache.erase(it);
} else {
++it;
}
}
}
public:
CacheManager(size_t size = 100, int ageSeconds = 3600)
: maxSize(size), maxAge(ageSeconds) {}
void put(const std::string& key, const T& value) {
removeExpired();
evictIfNeeded();
auto obj = std::make_shared<CachedObject<T>>(key, value);
cache[key] = obj;
std::cout << "[Cache] Stored: " << key << std::endl;
}
T get(const std::string& key) {
auto it = cache.find(key);
if (it != cache.end() && it->second->isValid) {
it->second->markAccessed(); // ✅ 访问私有成员
std::cout << "[Cache] Hit: " << key << std::endl;
return it->second->data;
}
std::cout << "[Cache] Miss: " << key << std::endl;
return T();
}
void invalidate(const std::string& key) {
auto it = cache.find(key);
if (it != cache.end()) {
it->second->invalidate(); // ✅ 访问私有成员
std::cout << "[Cache] Invalidated: " << key << std::endl;
}
}
void printStats() const {
std::cout << "\n=== Cache Statistics ===" << std::endl;
std::cout << "Size: " << cache.size() << "/" << maxSize << std::endl;
for (const auto& pair : cache) {
std::cout << " " << pair.first
<< " (age: " << pair.second->getAge().count()
<< "s, accesses: " << pair.second->accessCount << ")" << std::endl;
}
}
};
#endif
// File: main.cpp
#include "cache.h"
#include <thread>
int main() {
CacheManager<std::string> cache(5, 10); // 最大5个,10秒过期
// 存入数据
cache.put("user:1", "Alice");
cache.put("user:2", "Bob");
cache.put("user:3", "Charlie");
// 读取数据
std::cout << "\nGetting user:1: " << cache.get("user:1") << std::endl;
std::cout << "Getting user:2: " << cache.get("user:2") << std::endl;
std::cout << "Getting user:4: " << cache.get("user:4") << std::endl; // Miss
// 再次读取(增加访问计数)
cache.get("user:1");
cache.get("user:1");
// 使一个缓存失效
cache.invalidate("user:2");
cache.get("user:2"); // 应该 Miss
// 等待过期
std::cout << "\nWaiting 11 seconds for expiration..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(11));
cache.get("user:1"); // 应该过期了
cache.get("user:3"); // 应该过期了
cache.printStats();
return 0;
}
📊 输出结果
[Cache] Stored: user:1
[Cache] Stored: user:2
[Cache] Stored: user:3
[Cache] Hit: user:1
Getting user:1: Alice
[Cache] Hit: user:2
Getting user:2: Bob
[Cache] Miss: user:4
Getting user:4:
[Cache] Hit: user:1
[Cache] Hit: user:1
[Cache] Invalidated: user:2
[Cache] Miss: user:2
Waiting 11 seconds for expiration...
[Cache] Miss: user:1
[Cache] Miss: user:3
=== Cache Statistics ===
Size: 0/5
场景11:观察者模式
🎯 为什么用友元?
被观察者需要访问观察者的私有更新方法,观察者需要访问被观察者的私有状态。
✅ 完整代码示例
// File: observer.h
#ifndef OBSERVER_H
#define OBSERVER_H
#include <iostream>
#include <string>
#include <vector>
#include <memory>
class Subject;
class Observer {
friend class Subject;
private:
std::string name;
std::string lastUpdate;
protected:
virtual void update(const std::string& message) {
lastUpdate = message;
std::cout << "[" << name << "] Received: " << message << std::endl;
}
public:
Observer(const std::string& n) : name(n) {}
virtual ~Observer() = default;
std::string getName() const { return name; }
std::string getLastUpdate() const { return lastUpdate; }
};
class Subject {
friend class ConcreteObserver;
private:
std::vector<std::shared_ptr<Observer>> observers;
std::string state;
int stateVersion;
public:
Subject() : stateVersion(0) {}
void attach(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
std::cout << "[Subject] Attached observer: " << obs->name << std::endl;
}
void detach(const std::string& name) {
observers.erase(
std::remove_if(observers.begin(), observers.end(),
[&name](const std::shared_ptr<Observer>& obs) {
return obs->name == name;
}),
observers.end()
);
std::cout << "[Subject] Detached observer: " << name << std::endl;
}
void setState(const std::string& newState) {
state = newState;
stateVersion++;
std::cout << "[Subject] State changed to: " << state << std::endl;
notify();
}
void notify() {
std::string message = "State v" + std::to_string(stateVersion) + ": " + state;
for (auto& obs : observers) {
obs->update(message); // ✅ 访问 Observer 的 protected 方法
}
}
std::string getState() const { return state; }
int getStateVersion() const { return stateVersion; }
};
class ConcreteObserver : public Observer {
private:
int updateCount;
public:
ConcreteObserver(const std::string& n) : Observer(n), updateCount(0) {}
void update(const std::string& message) override {
Observer::update(message);
updateCount++;
std::cout << "[" << name << "] Update count: " << updateCount << std::endl;
}
int getUpdateCount() const { return updateCount; }
};
#endif
// File: main.cpp
#include "observer.h"
#include <algorithm>
int main() {
Subject subject;
auto obs1 = std::make_shared<ConcreteObserver>("Observer1");
auto obs2 = std::make_shared<ConcreteObserver>("Observer2");
auto obs3 = std::make_shared<ConcreteObserver>("Observer3");
subject.attach(obs1);
subject.attach(obs2);
subject.attach(obs3);
std::cout << "\n--- Changing state ---" << std::endl;
subject.setState("Initial State");
std::cout << "\n--- Changing state again ---" << std::endl;
subject.setState("Updated State");
std::cout << "\n--- Detaching Observer2 ---" << std::endl;
subject.detach("Observer2");
std::cout << "\n--- Changing state after detach ---" << std::endl;
subject.setState("Final State");
std::cout << "\n=== Observer Statistics ===" << std::endl;
std::cout << "Observer1 updates: " << obs1->getUpdateCount() << std::endl;
std::cout << "Observer2 updates: " << obs2->getUpdateCount() << std::endl;
std::cout << "Observer3 updates: " << obs3->getUpdateCount() << std::endl;
return 0;
}
📊 输出结果
[Subject] Attached observer: Observer1
[Subject] Attached observer: Observer2
[Subject] Attached observer: Observer3
--- Changing state ---
[Subject] State changed to: Initial State
[Observer1] Received: State v1: Initial State
[Observer1] Update count: 1
[Observer2] Received: State v1: Initial State
[Observer2] Update count: 1
[Observer3] Received: State v1: Initial State
[Observer3] Update count: 1
--- Changing state again ---
[Subject] State changed to: Updated State
[Observer1] Received: State v2: Updated State
[Observer1] Update count: 2
[Observer2] Received: State v2: Updated State
[Observer2] Update count: 2
[Observer3] Received: State v2: Updated State
[Observer3] Update count: 2
--- Detaching Observer2 ---
[Subject] Detached observer: Observer2
--- Changing state after detach ---
[Subject] State changed to: Final State
[Observer1] Received: State v3: Final State
[Observer1] Update count: 3
[Observer3] Received: State v3: Final State
[Observer3] Update count: 3
=== Observer Statistics ===
Observer1 updates: 3
Observer2 updates: 2
Observer3 updates: 3
场景12:智能指针
🎯 为什么用友元?
智能指针需要访问被管理对象的私有析构函数和引用计数。
✅ 完整代码示例
// File: smart_ptr.h
#ifndef SMART_PTR_H
#define SMART_PTR_H
#include <iostream>
#include <atomic>
template<typename T>
class SharedPtr;
template<typename T>
class ControlBlock {
friend class SharedPtr<T>;
private:
T* ptr;
std::atomic<int> refCount;
ControlBlock(T* p) : ptr(p), refCount(1) {}
~ControlBlock() {
delete ptr;
}
};
template<typename T>
class SharedPtr {
private:
ControlBlock<T>* control;
void release() {
if (control && --control->refCount == 0) {
delete control;
}
}
public:
explicit SharedPtr(T* ptr = nullptr)
: control(ptr ? new ControlBlock<T>(ptr) : nullptr) {}
// 拷贝构造函数
SharedPtr(const SharedPtr& other) : control(other.control) {
if (control) {
control->refCount++;
}
}
// 拷贝赋值
SharedPtr& operator=(const SharedPtr& other) {
if (this != &other) {
release();
control = other.control;
if (control) {
control->refCount++;
}
}
return *this;
}
// 移动构造函数
SharedPtr(SharedPtr&& other) noexcept : control(other.control) {
other.control = nullptr;
}
// 移动赋值
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
release();
control = other.control;
other.control = nullptr;
}
return *this;
}
T& operator*() const { return *control->ptr; }
T* operator->() const { return control->ptr; }
T* get() const { return control ? control->ptr : nullptr; }
int useCount() const {
return control ? control->refCount.load() : 0;
}
~SharedPtr() {
release();
}
};
// 测试类
class TestClass {
friend class ControlBlock<TestClass>;
private:
int value;
std::string name;
public:
TestClass(int v, const std::string& n) : value(v), name(n) {
std::cout << "[TestClass] Created: " << name << std::endl;
}
~TestClass() {
std::cout << "[TestClass] Destroyed: " << name << std::endl;
}
int getValue() const { return value; }
std::string getName() const { return name; }
};
#endif
// File: main.cpp
#include "smart_ptr.h"
void testSharedPtr() {
std::cout << "=== SharedPtr Test ===" << std::endl;
SharedPtr<TestClass> p1(new TestClass(10, "Object1"));
std::cout << "p1 use count: " << p1.useCount() << std::endl;
{
SharedPtr<TestClass> p2 = p1;
std::cout << "p2 use count: " << p2.useCount() << std::endl;
SharedPtr<TestClass> p3 = p2;
std::cout << "p3 use count: " << p3.useCount() << std::endl;
std::cout << "Value: " << p3->getValue() << std::endl;
}
std::cout << "After p2, p3 destroyed, p1 use count: " << p1.useCount() << std::endl;
}
void testMove() {
std::cout << "\n=== Move Test ===" << std::endl;
SharedPtr<TestClass> p1(new TestClass(20, "Object2"));
std::cout << "p1 use count: " << p1.useCount() << std::endl;
SharedPtr<TestClass> p2 = std::move(p1);
std::cout << "After move, p1 use count: " << p1.useCount() << std::endl;
std::cout << "After move, p2 use count: " << p2.useCount() << std::endl;
std::cout << "Value: " << p2->getValue() << std::endl;
}
int main() {
testSharedPtr();
testMove();
std::cout << "\n=== Program End ===" << std::endl;
return 0;
}
📊 输出结果
=== SharedPtr Test ===
[TestClass] Created: Object1
p1 use count: 1
p2 use count: 2
p3 use count: 3
Value: 10
[TestClass] Destroyed: Object1
After p2, p3 destroyed, p1 use count: 1
=== Move Test ===
[TestClass] Created: Object2
p1 use count: 1
After move, p1 use count: 0
After move, p2 use count: 1
Value: 20
[TestClass] Destroyed: Object2
=== Program End ===
[TestClass] Destroyed: Object1
📋 友元使用决策树
需要访问私有成员?
│
├─ 否 → 不需要友元 ✅
│
└─ 是 → 该函数应该是成员函数吗?
│
├─ 是 → 定义为成员函数 ✅
│
└─ 否 → 左操作数必须是其他类型吗?(如 ostream)
│
├─ 是 → 使用友元函数 ✅(场景1)
│
└─ 否 → 需要对称性吗?(如 a+b 和 b+a)
│
├─ 是 → 使用友元函数 ✅(场景2)
│
└─ 否 → 需要访问多个类的私有成员吗?
│
├─ 是 → 使用友元函数 ✅(场景3)
│
└─ 否 → 是工厂/单例/测试吗?
│
├─ 是 → 使用友元 ✅(场景4-5-9)
│
└─ 否 → 考虑公有接口 ✅
⚠️ 友元使用陷阱与最佳实践
陷阱1:过度使用友元
// ❌ 坏设计:所有函数都是友元
class MyClass {
friend void func1(MyClass&);
friend void func2(MyClass&);
friend void func3(MyClass&);
friend void func4(MyClass&);
// ... 太多友元 = 没有封装
private:
int data;
};
// ✅ 好设计:最小化友元
class MyClass {
friend std::ostream& operator<<(std::ostream&, const MyClass&);
private:
int data;
public:
int getData() const { return data; }
void setData(int d) { data = d; }
};
陷阱2:友元不传递
class A {
friend void func(A&);
private:
int x;
};
class B {
// func 不是 B 的友元!
private:
int y;
};
void func(A& a) {
a.x = 10; // ✅ 可以
// b.y = 20; // ❌ 错误
}
陷阱3:友元不继承
class Base {
friend void func(Base&);
protected:
int x;
};
class Derived : public Base {
// func 不是 Derived 的友元!
private:
int y;
};
最佳实践清单
| 原则 | 说明 |
|---|---|
| 最小化 | 只声明必要的友元 |
| 文档化 | 注释说明为什么需要这个友元 |
| 优先公有接口 | 能通过公有成员解决的,不用友元 |
| 避免友元类 | 友元函数比友元类更安全 |
| 谨慎跨模块 | 不同库之间尽量避免友元 |
| 测试隔离 | 测试代码用友元,生产代码不用 |
🎓 总结
| 场景 | 必要性 | 推荐度 | 关键原因 |
|---|---|---|---|
| 输入输出运算符 | 必须 | ⭐⭐⭐⭐⭐ | 左操作数不是类对象 |
| 对称运算符 | 必须 | ⭐⭐⭐⭐⭐ | 保证 a+b 和 b+a 都工作 |
| 跨类操作 | 必须 | ⭐⭐⭐⭐ | 访问多个类私有成员 |
| 工厂模式 | 推荐 | ⭐⭐⭐⭐ | 控制对象创建 |
| 单例模式 | 推荐 | ⭐⭐⭐⭐ | 防止外部创建 |
| 迭代器 | 推荐 | ⭐⭐⭐⭐⭐ | 访问容器内部结构 |
| 矩阵运算 | 推荐 | ⭐⭐⭐⭐ | 对称性和多对象访问 |
| 序列化 | 推荐 | ⭐⭐⭐⭐⭐ | 访问所有私有数据 |
| 测试框架 | 推荐 | ⭐⭐⭐⭐ | 验证私有成员 |
| 缓存系统 | 推荐 | ⭐⭐⭐⭐ | 管理对象状态 |
| 观察者模式 | 推荐 | ⭐⭐⭐⭐ | 双向访问需求 |
| 智能指针 | 必须 | ⭐⭐⭐⭐⭐ | 管理对象生命周期 |
记住:友元是打破封装的"特权",应该像使用特权一样谨慎! 🔒
关注我,后面更精彩。

1083

被折叠的 条评论
为什么被折叠?



