《C++ Primer》第5版 友元 (friend)
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 关键字开始的函数声明语句即可:
classSales_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:忘记在类外重新声明非成员函数
classSales_data{friend Sales_data add(const Sales_data&,const Sales_data&);// ...};// ❌ 错误:缺少类外声明!编译器可能找不到这个函数 Sales_data add(const Sales_data& lhs,const Sales_data& rhs){// ...}✅ 正确做法:
classSales_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:误以为友元是双向的
classA{friendvoidfunc(A&, B&);// func 可以访问 A 的私有成员};classB{// ❌ func 不能访问 B 的私有成员,除非 B 也声明 func 为友元};✅ 正确做法:
classA{friendvoidfunc(A&, B&);};classB{friendvoidfunc(A&, B&);// 必须也声明};错误3:在友元声明中使用错误的参数类型
classSales_data{friend std::ostream&print(std::ostream&, Sales_data&);// ❌ 应该是 const 引用};✅ 正确做法:
friend std::ostream&print(std::ostream&,const Sales_data&);// ✅ 只读,安全高效💡 实际应用示例
假设你有一个 Matrix 类,想重载 operator<< 输出矩阵:
classMatrix{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(constauto& row : m.data){// 访问私有成员 datafor(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>classPerson{// 声明友元函数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;}// 使用intmain(){ Person p("Alice",25,"[email protected]"); std::cout << p << std::endl;// 链式输出 Person p2; std::cin >> p2;// 链式输入 std::cout << p2 << std::endl;return0;}📊 对比:成员函数 vs 友元函数
| 方式 | 代码 | 问题 |
|---|---|---|
| ❌ 成员函数 | p.operator<<(cout) | 语法反直觉,左操作数必须是对象 |
| ✅ 友元函数 | cout << p | 语法自然,符合习惯 |
2️⃣ 对称二元运算符重载
🎯 为什么必须用友元?
当运算符的两个操作数地位平等时(如 +, -, ==),如果定义为成员函数,左操作数必须是类对象,这会导致不对称:
// 成员函数版本 Complex c1, c2; c1 + c2;// ✅ 可以:c1.operator+(c2)5+ c2;// ❌ 错误:5 没有 operator+ 成员函数✅ 代码示例
#include<iostream>classComplex{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){}voidprint()const{ std::cout << real <<" + "<< imag <<"i"<< std::endl;}};// 两个 Complex 相加 Complex operator+(const Complex& c1,const Complex& c2){returnComplex(c1.real + c2.real, c1.imag + c2.imag);}// Complex + double Complex operator+(const Complex& c,double d){returnComplex(c.real + d, c.imag);}// double + Complex(必须是友元!) Complex operator+(double d,const Complex& c){returnComplex(c.real + d, c.imag);}// 使用intmain(){ Complex c1(1,2); Complex c2(3,4); c1 + c2;// ✅ 两边都是 Complex c1 +5.0;// ✅ Complex + double5.0+ c1;// ✅ double + Complex(成员函数无法实现!) c1.print();return0;}📊 对称性对比
| 运算符 | 成员函数 | 友元函数 |
|---|---|---|
c1 + c2 | ✅ | ✅ |
c1 + 5.0 | ✅ | ✅ |
5.0 + c1 | ❌ | ✅ |
3️⃣ 访问多个类私有成员的函数
🎯 为什么必须用友元?
当一个函数需要同时访问两个或多个类的私有成员时,必须将这些类都声明该函数为友元。
✅ 代码示例
#include<iostream>#include<string>classDate;// 前向声明classPerson{friendvoidcompareAges(const Person&,const Person&);friendvoidprintPersonDate(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;}};classDate{friendvoidprintPersonDate(const Person&,const Date&);private:int year, month, day;public:Date(int y,int m,int d):year(y),month(m),day(d){}};// 需要访问两个类的私有成员voidprintPersonDate(const Person& p,const Date& d){ std::cout << p.name <<" was born on "// ✅ 访问 Person 私有成员<< d.year <<"-"<< d.month <<"-"<< d.day // ✅ 访问 Date 私有成员<< std::endl;}// 比较两个 Person 的年龄voidcompareAges(const Person& p1,const Person& p2){if(p1.age > p2.age)// ✅ 访问私有成员 std::cout << p1.name <<" is older"<< std::endl;elseif(p1.age < p2.age) std::cout << p2.name <<" is older"<< std::endl;else std::cout <<"Same age"<< std::endl;}// 使用intmain(){ Person p1("Alice",25); Person p2("Bob",30); Date d(1998,5,15);compareAges(p1, p2);printPersonDate(p1, d);return0;}4️⃣ 工厂函数(Factory Function)
🎯 为什么用友元?
当类有私有构造函数(如单例模式、对象池),但需要特定函数来创建对象时。
✅ 代码示例
#include<iostream>#include<memory>#include<vector>classDatabase{// 声明工厂函数为友元friend std::unique_ptr<Database>createDatabase(const std::string& connStr);friendclassDatabasePool;// 友元类private: std::string connectionString;bool connected;// 私有构造函数:防止外部直接创建Database(const std::string& conn):connectionString(conn),connected(false){ std::cout <<"Database created (private constructor)"<< std::endl;}public:voidconnect(){ 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);// ✅ 可以访问私有构造函数}// 对象池类(友元类)classDatabasePool{public: std::vector<std::unique_ptr<Database>> pools;voidaddDatabase(const std::string& connStr){ pools.push_back(std::make_unique<Database>(connStr));// ✅ 可以访问私有构造函数}};// 使用intmain(){// ❌ 错误:不能直接创建// Database db("mysql://localhost");// ✅ 正确:通过工厂函数创建auto db =createDatabase("mysql://localhost"); db->connect();// ✅ 正确:通过对象池创建 DatabasePool pool; pool.addDatabase("mysql://db1"); pool.addDatabase("mysql://db2");return0;}5️⃣ 工具/辅助函数
🎯 为什么用友元?
当需要一个独立于类的工具函数,但又需要访问类的内部实现时。
✅ 代码示例
#include<iostream>#include<cmath>classVector3D{frienddoubledotProduct(const Vector3D&,const Vector3D&);friend Vector3D crossProduct(const Vector3D&,const Vector3D&);frienddoubledistance(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){}doublemagnitude()const{return std::sqrt(x*x + y*y + z*z);}};// 点积(需要访问两个向量的私有成员)doubledotProduct(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){returnVector3D( v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x );}// 计算两点距离doubledistance(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);}// 使用intmain(){ 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;return0;}6️⃣ 迭代器模式
🎯 为什么用友元?
迭代器需要访问容器的内部数据结构(如链表节点、数组指针),但不应该是容器的成员。
✅ 代码示例
#include<iostream>classLinkedList;// 前向声明classIterator{friendclassLinkedList;// LinkedList 是 Iterator 的友元private:structNode{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;}booloperator!=(const Iterator& other)const{return current != other.current;}};classLinkedList{friendclassIterator;// Iterator 是 LinkedList 的友元private:structNode{int data; Node* next;Node(int d):data(d),next(nullptr){}}; Node* head;public:LinkedList():head(nullptr){}voidpushFront(int value){ Node* newNode =newNode(value); newNode->next = head; head = newNode;}// 返回迭代器(可以访问私有 Node 结构) Iterator begin(){returnIterator(head);// ✅ 可以访问 Iterator 的私有构造函数} Iterator end(){returnIterator(nullptr);}};// 使用intmain(){ 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;return0;}7️⃣ 单例模式(Singleton)
🎯 为什么用友元?
单例模式需要私有构造函数防止外部创建,但需要特定方法获取唯一实例。
✅ 代码示例
#include<iostream>classSingleton{// 声明获取实例的函数为友元(可选,通常用静态成员函数)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:voidsetData(int d){ data = d;}intgetData()const{return data;}// 析构函数~Singleton(){ std::cout <<"Singleton destroyed"<< std::endl;}};// 静态成员初始化 Singleton* Singleton::instance =nullptr;// 友元函数:获取唯一实例 Singleton&getInstance(){if(Singleton::instance ==nullptr){ Singleton::instance =newSingleton();// ✅ 可以调用私有构造函数}return*Singleton::instance;}// 使用intmain(){// ❌ 错误:不能直接创建// 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;return0;}⚠️ 友元使用的注意事项
1. 友元破坏封装性
// ❌ 滥用友元:所有函数都是友元classMyClass{friendvoidfunc1(MyClass&);friendvoidfunc2(MyClass&);friendvoidfunc3(MyClass&);// ... 太多友元 = 没有封装private:int data;};// ✅ 合理使用:仅必要的函数是友元classMyClass{friend std::ostream&operator<<(std::ostream&,const MyClass&);private:int data;public:intgetData()const{return data;}// 通过公有接口访问};2. 友元不传递
classA{friendvoidfunc(A&);private:int x;};classB{// func 不是 B 的友元,即使 func 是 A 的友元private:int y;};voidfunc(A& a){ a.x =10;// ✅ 可以// b.y = 20; // ❌ 错误:func 不是 B 的友元}3. 友元不继承
classBase{friendvoidfunc(Base&);protected:int x;};classDerived:publicBase{// func 不是 Derived 的友元!private:int y;};voidfunc(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#ifndefSTUDENT_H#defineSTUDENT_H#include<iostream>#include<string>#include<vector>classStudent{// 友元声明:输入输出运算符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){}voidaddCourse(const std::string& course){ courses.push_back(course);} std::string getName()const{return name;}intgetId()const{return id;}doublegetGPA()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"intmain(){ 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();return0;}📊 对比:成员函数 vs 友元函数
| 特性 | 成员函数(不可行) | 友元函数(推荐) |
|---|---|---|
| 语法 | s.operator<<(cout) | cout << s |
| 链式调用 | ❌ 困难 | ✅ 自然 |
| 左操作数 | 必须是 Student | 可以是 ostream |
| 符合习惯 | ❌ 反直觉 | ✅ 符合直觉 |
场景2:对称二元运算符
🎯 为什么必须用友元?
确保 a + b 和 b + a 都能工作,即使其中一个不是类对象。
✅ 完整代码示例
// File: rational.h#ifndefRATIONAL_H#defineRATIONAL_H#include<iostream>#include<numeric>classRational{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&);friendbooloperator==(const Rational&,const Rational&);friendbooloperator==(const Rational&,int);friendbooloperator==(int,const Rational&);friend std::ostream&operator<<(std::ostream&,const Rational&);private:int numerator;int denominator;voidsimplify(){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();}doubletoDouble()const{returnstatic_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;returnRational(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){returnRational(i,1)+ r;}// Rational * Rational Rational operator*(const Rational& r1,const Rational& r2){returnRational(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){returnRational(i,1)* r;}// 比较运算符booloperator==(const Rational& r1,const Rational& r2){return r1.numerator == r2.numerator && r1.denominator == r2.denominator;}booloperator==(const Rational& r,int i){return r ==Rational(i,1);}booloperator==(int i,const Rational& r){returnRational(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"intmain(){ 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;// 1return0;}📊 对称性测试结果
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#ifndefGEOMETRY_H#defineGEOMETRY_H#include<iostream>#include<cmath>#include<string>classPoint;classRectangle;// 跨类操作函数声明doubledistance(const Point&,const Point&);boolcontains(const Rectangle&,const Point&);voidprintRelation(const Rectangle&,const Point&);classPoint{frienddoubledistance(const Point&,const Point&);friendboolcontains(const Rectangle&,const Point&);friendvoidprintRelation(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)+")";}};classRectangle{friendboolcontains(const Rectangle&,const Point&);friendvoidprintRelation(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){}doublegetWidth()const{return bottomRight.x - topLeft.x;}doublegetHeight()const{return topLeft.y - bottomRight.y;}doublegetArea()const{returngetWidth()*getHeight();}};// 计算两点距离(访问两个 Point 的私有成员)doubledistance(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 的私有成员)boolcontains(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;}// 打印关系信息voidprintRelation(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"intmain(){ 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);return0;}📊 输出结果
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#ifndefVEHICLE_H#defineVEHICLE_H#include<iostream>#include<string>#include<memory>#include<vector>classVehicleFactory;classVehicle{friendclassVehicleFactory;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;}};// 工厂类classVehicleFactory{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();}staticvoidprintInventory(){ std::cout <<"\n=== Vehicle Inventory ==="<< std::endl;for(constauto& 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"intmain(){// ❌ 错误:不能直接创建// 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();return0;}📊 输出结果
[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#ifndefLOGGER_H#defineLOGGER_H#include<iostream>#include<fstream>#include<string>#include<mutex>#include<vector>classLogger{// 友元函数:获取唯一实例friend Logger&getLogger();friendvoiddestroyLogger();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:voidsetLogFile(const std::string& file){ logFile = file;}voidenableFileLogging(bool enable){ fileEnabled = enable;}voidlog(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();}}}voidlogError(const std::string& message){log("[ERROR] "+ message);}voidlogWarning(const std::string& message){log("[WARNING] "+ message);}voidprintHistory()const{ std::cout <<"\n=== Log History ==="<< std::endl;for(constauto& 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 =newLogger();}return*Logger::instance;}// 销毁单例voiddestroyLogger(){ std::lock_guard<std::mutex>lock(Logger::mutex);delete Logger::instance; Logger::instance =nullptr;}#endif// File: main.cpp#include"logger.h"voidsomeFunction(){ Logger& logger =getLogger(); logger.log("Entering someFunction");// ... 做一些事情 logger.log("Exiting someFunction");}intmain(){ 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();return0;}📊 输出结果
[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#ifndefSIMPLE_VECTOR_H#defineSIMPLE_VECTOR_H#include<iostream>#include<memory>#include<initializer_list>template<typenameT>classSimpleVector;template<typenameT>classVectorIterator{friendclassSimpleVector<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;}booloperator==(const VectorIterator& other)const{return ptr == other.ptr;}booloperator!=(const VectorIterator& other)const{return ptr != other.ptr;}};template<typenameT>classSimpleVector{friendclassVectorIterator<T>;private: T* data; size_t size; size_t capacity;voidresize(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(constauto& elem : init){ data[i++]= elem;}}~SimpleVector(){delete[] data;}// 禁止拷贝(简化示例)SimpleVector(const SimpleVector&)=delete; SimpleVector&operator=(const SimpleVector&)=delete;voidpushBack(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;}boolempty()const{return size ==0;}// 返回迭代器 VectorIterator<T>begin(){returnVectorIterator<T>(data);} VectorIterator<T>end(){returnVectorIterator<T>(data + size);} VectorIterator<T>begin()const{returnVectorIterator<T>(data);} VectorIterator<T>end()const{returnVectorIterator<T>(data + size);}};#endif// File: main.cpp#include"simple_vector.h"intmain(){ 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;return0;}📊 输出结果
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#ifndefMATRIX_H#defineMATRIX_H#include<iostream>#include<vector>#include<stdexcept>classMatrix{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(constauto& 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];}doubleat(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"intmain(){ 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;return0;}📊 输出结果
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#ifndefSERIALIZABLE_H#defineSERIALIZABLE_H#include<iostream>#include<fstream>#include<sstream>#include<string>#include<vector>classPerson;// 序列化函数声明 std::string serialize(const Person& p); Person deserialize(const std::string& data);boolsaveToFile(const Person& p,const std::string& filename); Person loadFromFile(const std::string& filename);classPerson{friend std::string serialize(const Person& p);friend Person deserialize(const std::string& data);friendboolsaveToFile(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){}voidaddHobby(const std::string& hobby){ hobbies.push_back(hobby);}voidprint()const{ std::cout <<"Name: "<< name <<", Age: "<< age <<", Email: "<< email << std::endl; std::cout <<"Hobbies: ";for(constauto& 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;}// 保存到文件boolsaveToFile(const Person& p,const std::string& filename){ std::ofstream file(filename);if(!file.is_open())returnfalse; file <<serialize(p); file.close();returntrue;}// 从文件加载 Person loadFromFile(const std::string& filename){ std::ifstream file(filename); std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); file.close();returndeserialize(content);}#endif// File: main.cpp#include"serializable.h"intmain(){ Person p1("Alice",25,"[email protected]"); 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();return0;}📊 输出结果
Original Person: Name: Alice, Age: 25, Email: [email protected] Hobbies: Reading Coding Hiking Serialized JSON: {"name":"Alice","age":25,"email":"[email protected]","hobbies":["Reading","Coding","Hiking"]} Saved to person.json Loaded Person: Name: Alice, Age: 25, Email: [email protected] Hobbies: Deserialized Person: Name: Alice, Age: 25, Email: [email protected] Hobbies: 场景9:测试框架
🎯 为什么用友元?
测试代码需要访问被测试类的私有成员进行验证,但不应该修改生产代码的访问权限。
✅ 完整代码示例
// File: calculator.h#ifndefCALCULATOR_H#defineCALCULATOR_H#include<iostream>#include<vector>classCalculatorTest;classCalculator{friendclassCalculatorTest;private:double memory; std::vector<double> history;int operationCount;doubleinternalCalculate(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){}doubleadd(double a,double b){double result =internalCalculate(a, b,'+'); history.push_back(result); operationCount++;return result;}doublesubtract(double a,double b){double result =internalCalculate(a, b,'-'); history.push_back(result); operationCount++;return result;}doublemultiply(double a,double b){double result =internalCalculate(a, b,'*'); history.push_back(result); operationCount++;return result;}doubledivide(double a,double b){double result =internalCalculate(a, b,'/'); history.push_back(result); operationCount++;return result;}voidstoreToMemory(double value){ memory = value;}doublerecallFromMemory()const{return memory;}intgetOperationCount()const{return operationCount;}};// 测试类classCalculatorTest{public:staticvoidtestInternalCalculate(){ 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;}staticvoidtestPrivateMembers(){ 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;}staticvoidrunAllTests(){ std::cout <<"=== Calculator Test Suite ==="<< std::endl;testInternalCalculate();testPrivateMembers(); std::cout <<"\n=== All Tests Completed ==="<< std::endl;}};#endif// File: main.cpp#include"calculator.h"intmain(){// 运行测试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;return0;}📊 输出结果
=== 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#ifndefCACHE_H#defineCACHE_H#include<iostream>#include<string>#include<unordered_map>#include<chrono>#include<memory>template<typenameT>classCacheManager;template<typenameT>classCachedObject{friendclassCacheManager<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();}voidmarkAccessed(){ lastAccessed = std::chrono::system_clock::now(); accessCount++;}voidinvalidate(){ isValid =false;}public:const T&getData()const{return data;} std::string getKey()const{return key;}intgetAccessCount()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<typenameT>classCacheManager{private: std::unordered_map<std::string, std::shared_ptr<CachedObject<T>>> cache; size_t maxSize; std::chrono::seconds maxAge;voidevictIfNeeded(){if(cache.size()<= maxSize)return;// 简单的 LRU 策略:删除最久未访问的 std::string oldestKey;auto oldestTime = std::chrono::system_clock::now();for(constauto& 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);}}voidremoveExpired(){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){}voidput(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;returnT();}voidinvalidate(const std::string& key){auto it = cache.find(key);if(it != cache.end()){ it->second->invalidate();// ✅ 访问私有成员 std::cout <<"[Cache] Invalidated: "<< key << std::endl;}}voidprintStats()const{ std::cout <<"\n=== Cache Statistics ==="<< std::endl; std::cout <<"Size: "<< cache.size()<<"/"<< maxSize << std::endl;for(constauto& 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>intmain(){ 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();return0;}📊 输出结果
[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#ifndefOBSERVER_H#defineOBSERVER_H#include<iostream>#include<string>#include<vector>#include<memory>classSubject;classObserver{friendclassSubject;private: std::string name; std::string lastUpdate;protected:virtualvoidupdate(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;}};classSubject{friendclassConcreteObserver;private: std::vector<std::shared_ptr<Observer>> observers; std::string state;int stateVersion;public:Subject():stateVersion(0){}voidattach(std::shared_ptr<Observer> obs){ observers.push_back(obs); std::cout <<"[Subject] Attached observer: "<< obs->name << std::endl;}voiddetach(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;}voidsetState(const std::string& newState){ state = newState; stateVersion++; std::cout <<"[Subject] State changed to: "<< state << std::endl;notify();}voidnotify(){ 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;}intgetStateVersion()const{return stateVersion;}};classConcreteObserver:publicObserver{private:int updateCount;public:ConcreteObserver(const std::string& n):Observer(n),updateCount(0){}voidupdate(const std::string& message)override{Observer::update(message); updateCount++; std::cout <<"["<< name <<"] Update count: "<< updateCount << std::endl;}intgetUpdateCount()const{return updateCount;}};#endif// File: main.cpp#include"observer.h"#include<algorithm>intmain(){ 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;return0;}📊 输出结果
[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#ifndefSMART_PTR_H#defineSMART_PTR_H#include<iostream>#include<atomic>template<typenameT>classSharedPtr;template<typenameT>classControlBlock{friendclassSharedPtr<T>;private: T* ptr; std::atomic<int> refCount;ControlBlock(T* p):ptr(p),refCount(1){}~ControlBlock(){delete ptr;}};template<typenameT>classSharedPtr{private: ControlBlock<T>* control;voidrelease(){if(control &&--control->refCount ==0){delete control;}}public:explicitSharedPtr(T* ptr =nullptr):control(ptr ?newControlBlock<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;}intuseCount()const{return control ? control->refCount.load():0;}~SharedPtr(){release();}};// 测试类classTestClass{friendclassControlBlock<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;}intgetValue()const{return value;} std::string getName()const{return name;}};#endif// File: main.cpp#include"smart_ptr.h"voidtestSharedPtr(){ std::cout <<"=== SharedPtr Test ==="<< std::endl; SharedPtr<TestClass>p1(newTestClass(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;}voidtestMove(){ std::cout <<"\n=== Move Test ==="<< std::endl; SharedPtr<TestClass>p1(newTestClass(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;}intmain(){testSharedPtr();testMove(); std::cout <<"\n=== Program End ==="<< std::endl;return0;}📊 输出结果
=== 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:过度使用友元
// ❌ 坏设计:所有函数都是友元classMyClass{friendvoidfunc1(MyClass&);friendvoidfunc2(MyClass&);friendvoidfunc3(MyClass&);friendvoidfunc4(MyClass&);// ... 太多友元 = 没有封装private:int data;};// ✅ 好设计:最小化友元classMyClass{friend std::ostream&operator<<(std::ostream&,const MyClass&);private:int data;public:intgetData()const{return data;}voidsetData(int d){ data = d;}};陷阱2:友元不传递
classA{friendvoidfunc(A&);private:int x;};classB{// func 不是 B 的友元!private:int y;};voidfunc(A& a){ a.x =10;// ✅ 可以// b.y = 20; // ❌ 错误}陷阱3:友元不继承
classBase{friendvoidfunc(Base&);protected:int x;};classDerived:publicBase{// func 不是 Derived 的友元!private:int y;};最佳实践清单
| 原则 | 说明 |
|---|---|
| 最小化 | 只声明必要的友元 |
| 文档化 | 注释说明为什么需要这个友元 |
| 优先公有接口 | 能通过公有成员解决的,不用友元 |
| 避免友元类 | 友元函数比友元类更安全 |
| 谨慎跨模块 | 不同库之间尽量避免友元 |
| 测试隔离 | 测试代码用友元,生产代码不用 |
🎓 总结
| 场景 | 必要性 | 推荐度 | 关键原因 |
|---|---|---|---|
| 输入输出运算符 | 必须 | ⭐⭐⭐⭐⭐ | 左操作数不是类对象 |
| 对称运算符 | 必须 | ⭐⭐⭐⭐⭐ | 保证 a+b 和 b+a 都工作 |
| 跨类操作 | 必须 | ⭐⭐⭐⭐ | 访问多个类私有成员 |
| 工厂模式 | 推荐 | ⭐⭐⭐⭐ | 控制对象创建 |
| 单例模式 | 推荐 | ⭐⭐⭐⭐ | 防止外部创建 |
| 迭代器 | 推荐 | ⭐⭐⭐⭐⭐ | 访问容器内部结构 |
| 矩阵运算 | 推荐 | ⭐⭐⭐⭐ | 对称性和多对象访问 |
| 序列化 | 推荐 | ⭐⭐⭐⭐⭐ | 访问所有私有数据 |
| 测试框架 | 推荐 | ⭐⭐⭐⭐ | 验证私有成员 |
| 缓存系统 | 推荐 | ⭐⭐⭐⭐ | 管理对象状态 |
| 观察者模式 | 推荐 | ⭐⭐⭐⭐ | 双向访问需求 |
| 智能指针 | 必须 | ⭐⭐⭐⭐⭐ | 管理对象生命周期 |
记住:友元是打破封装的"特权",应该像使用特权一样谨慎! 🔒
关注我,后面更精彩。