《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&);
💡 关键语法点:
  1. friend 声明位置
    • 可以在 publicprotectedprivate 区域声明,不影响访问权限
    • 通常放在类定义开头或结尾,便于阅读。
  2. friend 声明形式
    • 就像普通函数声明,前面加 friend 关键字。
    • 不需要函数体(只是声明)。
    • 参数类型必须与实际定义完全匹配。
  3. 友元关系是单向的
    • Sales_data 声明 read 是友元 → read 可以访问 Sales_data 的私有成员。
    • read 不能反过来让 Sales_data 访问自己的私有成员(除非也声明)。
  4. 友元不传递、不继承
    • A 是 B 的友元,B 是 C 的友元 ≠ A 是 C 的友元。
    • 派生类不会自动继承基类的友元关系。
  5. 友元声明 ≠ 定义
    • 类内的 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 + bb + 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 都工作
跨类操作必须⭐⭐⭐⭐访问多个类私有成员
工厂模式推荐⭐⭐⭐⭐控制对象创建
单例模式推荐⭐⭐⭐⭐防止外部创建
迭代器推荐⭐⭐⭐⭐⭐访问容器内部结构
矩阵运算推荐⭐⭐⭐⭐对称性和多对象访问
序列化推荐⭐⭐⭐⭐⭐访问所有私有数据
测试框架推荐⭐⭐⭐⭐验证私有成员
缓存系统推荐⭐⭐⭐⭐管理对象状态
观察者模式推荐⭐⭐⭐⭐双向访问需求
智能指针必须⭐⭐⭐⭐⭐管理对象生命周期

记住:友元是打破封装的"特权",应该像使用特权一样谨慎! 🔒

关注我,后面更精彩。

Read more

MCP客户端与服务端初使用——让deepseek调用查询天气的mcp来查询天气

MCP客户端与服务端初使用——让deepseek调用查询天气的mcp来查询天气

本系列主要通过调用天气的mcp server查询天气这个例子来学习什么是mcp,以及怎么设计mcp。话不多说,我们开始吧。主要参考的是B站的老哥做的一个教程,我把链接放到这里,大家如果有什么不懂的也可以去看一下。 https://www.bilibili.com/video/BV1NLXCYTEbj?spm_id_from=333.788.videopod.episodes&vd_source=32148098d54c83926572ec0bab6a3b1d https://blog.ZEEKLOG.net/fufan_LLM/article/details/146377471 最终的效果:让deepseek-v3使用天气查询的工具来查询指定地方的天气情况 技术介绍 MCP,即Model Context Protocol(模型上下文协议),是由Claude的母公司Anthropic在2024年底推出的一项创新技术协议。在它刚问世时,并未引起太多关注,反响较为平淡。然而,随着今年智能体Agent领域的迅猛发展,MCP逐渐进入大众视野并受到广泛关注。今年2月,

By Ne0inhk
可以在命令行通过大模型使用上下文协议(MCP)与外部工具交互的软件:小巧的MCPHost

可以在命令行通过大模型使用上下文协议(MCP)与外部工具交互的软件:小巧的MCPHost

小巧的MCPHost MCPHost 可以在命令行下使用,使大型语言模型(LLM)能够通过模型上下文协议(MCP)与外部工具进行交互。目前支持Claude 3.5 Sonnet和Ollama等。本次实践使用自己架设的Deepseek v3模型,跑通了Time MCP服务。  官网:GitHub - mark3labs/mcphost: A CLI host application that enables Large Language Models (LLMs) to interact with external tools through the Model Context Protocol (MCP). 下载安装 使用非常方便,直接下载解压即可使用。官网提供Windows、Linux和MacOS三个系统的压缩包: https://github.com/

By Ne0inhk
实战篇:Python开发monogod数据库mcp server看完你就会了

实战篇:Python开发monogod数据库mcp server看完你就会了

原创不易,请关注公众号:【爬虫与大模型开发】,大模型的应用开发之路,整理了大模型在现在的企业级应用的实操及大家需要注意的一些AI开发的知识点!持续输出爬虫与大模型的相关文章。 前言 目前mcp协议是给deepseek大模型插上工具链的翅膀,让大模型不仅拥有超高的推理和文本生成能力,还能具备执行大脑意识的工具能力! 如何开发一个mcp? mcp是一种协议,指的是模型上下文协议 (Model Context Protocol)。 官方结成的mcp https://github.com/modelcontextprotocol/python-sdk mcp库 pip install mcp from mcp.server.fastmcp import FastMCP 我们先来做一个简单的案例 from mcp.server.fastmcp import FastMCP import requests mcp = FastMCP("spider") @mcp.tool() def crawl(

By Ne0inhk
【大模型实战篇】基于Claude MCP协议的智能体落地示例

【大模型实战篇】基于Claude MCP协议的智能体落地示例

1. 背景         之前我们在《MCP(Model Context Protocol) 大模型智能体第一个开源标准协议》一文中,介绍了MCP的概念,虽然了解了其概念、架构、解决的问题,但还缺少具体的示例,来帮助进一步理解整套MCP框架如何落地。         今天我们基于claude的官方例子--获取天气预报【1】,来理解MCP落地的整条链路。 2. MCP示例         该案例是构建一个简单的MCP天气预报服务器,并将其连接到主机,即Claude for Desktop。从基本设置开始,然后逐步发展到更复杂的使用场景。         大模型虽然能力非常强,但其弊端就是内容是过时的,这里的过时不是说内容很旧,只是表达内容具有非实时性。比如没有获取天气预报和严重天气警报的能力。因此我们将使用MCP来解决这一问题。         构建一个服务器,该服务器提供两个工具:获取警报(get-alerts)和获取预报(get-forecast)。然后,将该服务器连接到MCP主机(在本例中为Claude for Desktop)。         首先我们配置下环

By Ne0inhk