news 2026/10/23 2:59:01

现代c++第2.3章 友元函数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
现代c++第2.3章 友元函数

先澄清一个容易混淆的点

C++ 里没有「static class」这个语法。你在 C# 里写的static class MathHelper,或者 Java 里的static class Inner,在 C++ 里都不存在对应的关键字。C++ 的static放在不同位置,含义完全不同:

写法位置含义
class X { static int n; }类的静态成员(属于类,不属于对象)
class X { static void f(); }静态成员函数(没有 this 指针)
static int g;(全局/命名空间作用域)内部链接,只在本翻译单元可见
void f() { static int c; }局部静态变量,程序生命周期、首次执行时初始化
static class X {};编译错误,C++ 不允许

下面按这几类分别说规则。


一、类内的 static 数据成员

1. 声明和定义要分开(C++17 之前)

classCounter{staticinttotal;// 声明(在类体内)};intCounter::total=0;// 定义(必须在且只能在一个 .cpp 里)

类体内的只是声明,不算定义(除了下面第 3 条的特例)。漏掉类外定义,链接时会报 undefined reference。

C++17 起可以用inline,直接在类体内定义,不用写.cpp:

classCounter{inlinestaticinttotal=0;// C++17,等价于 inline 定义};

2. 不属于任何对象

  • 所有实例共享一份,sizeof(Counter)不包含它。
  • 即使一个对象都没创建,它也存在(程序启动时完成静态初始化)。
  • 访问推荐用Counter::total,虽然obj.total也合法但不推荐(容易误以为是实例成员)。

3. 可以在类体内直接给初始值的例外

  • const或constexpr的整型/枚举静态成员(C++98 起)。
  • constexpr静态成员(C++11 起,隐含 inline)。
  • 任何类型的inline static成员(C++17 起)。
classBuf{staticconstexprintSIZE=1024;// OKinlinestaticstd::string name="x";// C++17 OK};

注意:const static成员如果被odr-use(比如取地址&Buf::SIZE、绑定到引用),仍然需要一个类外的定义。

4. 可以是自身类的不完整类型

classNode{staticNode instance;// OK,静态成员不计入类大小// Node child; // 错误,非静态成员不能是不完整类型};

5. 访问控制照样生效。private static成员只能在类内和友元里访问,这点和实例成员一样。

6. 继承与模板

  • 静态成员会被派生类继承,也可以被同名成员隐藏,但不能被 override(重写是 virtual 的事)。
  • 类模板的静态成员:每个实例化版本各有一份,Foo<int>::count和Foo<double>::count是两个变量。

二、static 成员函数

核心规则:没有 this 指针。因此:

  • 不能直接访问非静态成员变量/函数,必须通过对象或指针:obj.x、p->f()。
  • 反过来,它可以访问私有的非静态成员(因为它仍是类的成员,受友元规则保护)。
  • 不能加 cv 限定和引用限定:static void f() const是错的。
  • 不能是 virtual:virtual static互斥,静态调用在编译期就绑定了,谈不上多态。
  • 类型是普通函数指针:void(*)(),可以直接当 C 风格回调传进去;而非静态成员函数指针类型是void(Class::*)(),两者不兼容——这是静态成员函数做回调的最大优势。
classTimer{public:staticvoidonTick(void*self);// 可传给 C APIprivate:intinterval;// 靠 self 参数反取};

三、类外的 static(文件作用域)

全局或命名空间里的static变量/函数 = 内部链接,只在当前.cpp可见,不会和其他翻译单元冲突。

staticinthelper();// 只有本文件能看见staticconstintBUF=64;

两个注意点:

  1. C++17 起更推荐用匿名命名空间代替static全局量:namespace { ... },语义更清晰,还能放类型定义。
  2. 头文件里的static const/static constexpr:每个包含它的.cpp都会生成一份独立副本。constexpr通常没问题(值一样,优化后无开销),但static的全局对象会有多份实例,可能踩坑。

四、函数内的局部 static

Logger&getLogger(){staticLogger inst;// 第一次走到这里才构造returninst;}
  • 生命周期到程序结束,只初始化一次。这就是著名的Meyers Singleton写法。
  • C++11 起保证线程安全(编译器会插入 once 机制),可以放心在多线程里用。C++11 之前不行。
  • 析构顺序与构造顺序相反,且发生在main退出之后。如果某个静态对象的析构函数去用另一个可能已被销毁的静态对象,就会崩溃——这是静态对象最常见的线上坑。对策:故意不析构(new出来不 delete)、用指针/std::optional,或者避免在析构函数里依赖其他全局状态。

五、最麻烦的一条:静态初始化顺序

同一个翻译单元内:按定义顺序初始化。
跨翻译单元:顺序未定义。

// a.cppintx=f();// f() 用到了 b.cpp 里的 y// b.cppinty=42;// 谁先跑?不知道

这就是Static Initialization Order Fiasco。通用解法是Construct On First Use:把全局量改成返回局部 static 引用的函数(见第四节代码),把“何时初始化”从链接期推迟到运行期第一次调用。


六、那「静态类」在 C++ 里怎么做?

如果你想要的是“一组工具函数,不需要实例化”,C++ 的惯用做法是命名空间 + 自由函数,而不是硬凑一个类:

namespacemath{intgcd(inta,intb);}

好处:没有多余的类名限定、支持 ADL 查找、可以被重载、不会诱导别人去继承或实例化。

如果确实要写成类(比如要配合模板参数、要封装一堆相关常量),常见套路是禁用实例化:

classMathfinal{Math()=delete;// 不能创建对象Math(constMath&)=delete;Math&operator=(constMath&)=delete;public:staticintgcd(inta,intb);inlinestaticconstexprdoublePI=3.14159265358979;};

顺带一提:C++ 的嵌套类天生就相当于 Java 的 static nested class——它不持有外部类的引用,也不需要任何static修饰。所以如果你在找“Java static 内部类”的对应物,直接用嵌套类即可。


七、速查清单(容易写错的地方)

  • ✅ 非 inline 的 static 数据成员必须在一个.cpp里定义,别放头文件。
  • ✅ C++17 优先用inline static,省去.cpp的定义。
  • ✅ 静态成员函数里不能用this,也不能直接碰非静态成员。
  • ✅static和virtual、const修饰互斥,别一起写。
  • ✅ 静态成员函数指针 = 普通函数指针,适合做回调;非静态的不行。
  • ✅ 跨文件的静态对象初始化顺序未定义,用“函数内局部 static”化解。
  • ✅ 局部 static 在 C++11 后线程安全,可放心做单例。
  • ✅ 别在静态对象的析构函数里依赖其他全局/静态对象。
  • ✅ 想做工具类,优先考虑namespace,其次才是删掉构造函数的全静态类。

在 C++ 中,友元(friend)的核心作用是打破封装,让某个函数或类能够访问另一个类的private和protected成员。虽然它破坏了面向对象编程的封装性,但在运算符重载、类之间紧密协作等场景下非常实用。

下面为你详细拆解友元函数和友元类的使用方法:

🤝 友元函数 (Friend Function)

友元函数不是该类的成员函数,但它可以像成员函数一样访问该类的所有成员。它通常在类内部用friend关键字声明,但定义在类的外部。

常见用途:运算符重载(如<<或>>)、两个不同类之间的数据交换。

代码示例:

#include<iostream>usingnamespacestd;classBox{private:doublewidth;public:Box(doublew):width(w){}// 声明友元函数// 注意:友元函数不属于 Box 类,所以没有 Box:: 作用域限定符friendvoidprintWidth(Box box);};// 友元函数的定义,可以直接访问 Box 的私有成员 widthvoidprintWidth(Box box){cout<<"Box 的宽度是: "<<box.width<<endl;}intmain(){Boxbox(10.0);// 像调用普通函数一样调用它printWidth(box);return0;}

🏢 友元类 (Friend Class)

如果类 A 是类 B 的友元类,那么类 A 中的所有成员函数都可以访问类 B 的私有和保护成员。

常见用途:当一个类需要为另一个类提供底层支持或管理功能时(例如链表类访问节点类的私有指针)。

代码示例:

#include<iostream>usingnamespacestd;classA;// 前向声明classB{private:intsecret=42;public:// 声明类 A 是类 B 的友元friendclassA;};classA{public:voiddisplay(B&b){// 因为 A 是 B 的友元,所以可以直接访问 b 的私有成员 secretcout<<"B 的秘密是: "<<b.secret<<endl;}};intmain(){A a;B b;a.display(b);return0;}

💡 核心注意事项

  1. 单向性:友元关系是单向的。如果 A 是 B 的友元,B 不一定是 A 的友元。
  2. 不传递:友元关系不能继承。如果 A 是 B 的友元,B 是 C 的友元,A 不一定是 C 的友元。
  3. 声明位置:friend关键字可以放在public、protected或private任何区域,效果是一样的。
  4. 谨慎使用:除非确实需要(比如重载operator<<),否则尽量少用友元,因为它会降低代码的模块化和安全性。

需要我帮你写一个用友元函数重载operator<<让类对象能直接cout输出的完整例子吗?

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 6:51:15

Redis十大隐形陷阱:从大Key到分布式锁的避坑指南

凌晨三点&#xff0c;微信群里的告警机器人突然开始刷屏&#xff0c;redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool。你揉着眼睛打开电脑&#xff0c;连接数打满&#xff0c;CPU 100%&#xff0c;INFO一查&#xff0c;key…

作者头像 李华
网站建设 2026/10/21 23:32:08

page_alloc is_check_pages_enabled

开关查询函数&#xff0c;用于判断"页分配/释放的调试检查是否启用"。它配合 free_page_is_bad()、check_new_page() 等使用。一、函数签名static inline bool is_check_pages_enabled(void)参数无参数。返回值bool&#xff1a;true 表示启用页检查&#xff1b;false…

作者头像 李华
网站建设 2026/10/22 17:48:00

Chroma实战:用轻量级向量数据库快速构建本地化RAG系统

做 RAG 项目这几年&#xff0c;我最大的感受是&#xff1a;真正决定系统上限的&#xff0c;往往不是大模型本身&#xff0c;而是你背后那套知识检索链路。而这条链路的起点&#xff0c;就是向量数据库。在尝试过好几款产品之后&#xff0c;我长期留在项目里的&#xff0c;是开源…

作者头像 李华
网站建设 2026/10/21 19:50:05

MySQL Windows服务启动失败1067错误根因与实战修复指南

简介&#xff1a;本资源是一份针对Windows环境下MySQL服务启动失败&#xff08;错误代码1067&#xff09;的实战排错指南&#xff0c;面向数据库初学者、运维人员及开发工程师&#xff0c;聚焦服务无法启动这一高频故障场景。内容基于真实问题排查过程展开&#xff0c;详细还原…

作者头像 李华
网站建设 2026/10/22 13:12:44

函数递归知识

函数递归知识 文章目录函数递归知识一.什么是递归&#xff08;一&#xff09;递归的思想&#xff08;二&#xff09;递归的限制条件二.递归举例&#xff08;一&#xff09;举例1&#xff1a;求n的阶乘1.分析和代码实现2.画图推演&#xff08;二&#xff09;举例2&#xff1a;顺…

作者头像 李华
网站建设 2026/10/20 10:02:39

11_给RAG建baseline_别被看起来不错骗了

给 RAG 建 baseline&#xff1a;别被"看起来不错"骗了 RAG 做到能跑、能答&#xff0c;很容易——跑几条查询&#xff0c;模型答得挺顺&#xff0c;就觉得自己做完了。但没有 baseline&#xff0c;后面所有优化都是自说自话&#xff1a;你不知道自己在进步还是退步&a…

作者头像 李华