news 2026/9/25 18:40:28

第22节 构造函数与初始化列表:解决const、引用成员初始化坑点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第22节 构造函数与初始化列表:解决const、引用成员初始化坑点

承接上文:上一节学习了类的成员变量、成员函数与工程编码规范,我们发现对象创建后成员变量默认是随机垃圾值,需要手动调用setter赋值。本节学习构造函数,对象创建时自动执行的特殊成员函数;再深入初始化列表,解决普通函数体内无法初始化const成员、引用成员的硬性语法问题,掌握对象初始化底层逻辑。

学习说明

构造函数是面向对象里对象生命周期的起点。很多新手分不清:在构造函数函数体内赋值,和初始化列表初始化有本质区别。 函数体内是赋值操作;初始化列表才是真正的初始化。const成员、引用成员必须在初始化列表完成初始化,不能在函数体内赋值,这是本节核心难点。

本节学习目标

  1. 掌握构造函数基础语法、调用时机、构造函数特征
  2. 理解默认构造函数,以及自定义构造后默认构造消失的规则
  3. 学会初始化列表写法,分清初始化 vs 赋值的本质差异
  4. 掌握const成员、引用成员为什么只能用初始化列表初始化
  5. 掌握初始化列表的执行顺序(和书写顺序无关,按成员声明顺序初始化)

一、构造函数基础

核心特点

  1. 函数名和类名完全相同,没有返回值,连void都不能写
  2. 对象创建的一瞬间自动调用,无法手动调用
  3. 可以重载(多个构造函数,参数列表不同)
  4. 如果自己不写任何构造,编译器自动生成默认无参构造;只要手写任意构造函数,编译器不再提供默认构造
#include <iostream> #include <string> using namespace std; class Person { private: string m_name; int m_age; public: // 无参构造函数 Person() { cout << "无参构造函数执行" << endl; m_name = "未知"; m_age = 0; } // 有参构造函数,构造重载 Person(string name, int age) { cout << "有参构造函数执行" << endl; m_name = name; m_age = age; } void showInfo() { cout << m_name << " " << m_age << endl; } }; int main() { Person p1; // 自动调用无参构造 Person p2("李四", 25); // 自动调用有参构造 p1.showInfo(); p2.showInfo(); return 0; }

上面写法是构造函数体内赋值,不是初始化。普通成员可以这么写,但const、引用成员不支持这种写法。

补充验证示例:手写有参构造后默认构造消失

#include <iostream> #include <string> using namespace std; class Person { public: Person(string name) { cout << "有参构造执行" << endl; } }; int main() { Person p1("张三"); // 正确 // Person p2; // 编译报错!手写构造后,编译器不再生成默认无参构造 return 0; }

C++11 default显式生成默认构造进阶示例

#include <iostream> #include <string> using namespace std; class Person { private: string m_name; public: Person(string name) { m_name = name; } Person() = default; // 显式告诉编译器,生成默认无参构造 }; int main() { Person p1("张三"); Person p2; // 正常编译运行 return 0; }

二、初始化列表语法

初始化列表写在构造函数参数括号后面、函数体{}之前,用冒号开头。

#include <iostream> #include <string> using namespace std; class Person { private: string m_name; int m_age; public: // 初始化列表写法 Person(string name, int age) : m_name(name), m_age(age) { cout << "初始化列表执行完成,进入构造函数体" << endl; } void showInfo() { cout << m_name << " " << m_age << endl; } }; int main() { Person p("王五", 30); p.showInfo(); return 0; }

核心规则:

  • 初始化列表先执行,然后才进入构造函数大括号内部
  • 初始化 = 创建变量同时给初始值;函数体内 = 变量已经创建完成,再修改值(赋值)

三、重点:const成员、引用成员只能使用初始化列表

const变量、引用变量,定义的时候就必须初始化,一旦创建完成,后续不能赋值。 放到构造函数体内时,成员已经完成创建,此时再赋值直接编译报错。只能在初始化列表阶段完成初始化。

#include <iostream> using namespace std; class Test { private: const int m_a; // const成员,必须初始化 int& m_ref; // 引用成员,必须初始化 public: // 只能在初始化列表初始化 const 和引用成员 Test(int val) : m_a(100), m_ref(val) { // m_a = 200; // 错误!const不能赋值 // m_ref = val; // 错误!引用已经绑定,不能在这里初始化 } }; int main() { int val = 42; Test t(val); // m_a = 100, m_ref绑定到变量val return 0; }

四、初始化列表容易踩坑:初始化顺序

⚠️初始化顺序由成员在类中声明的先后顺序决定,和初始化列表书写顺序无关!这是高频隐蔽bug。

#include <iostream> using namespace std; class Demo { private: int m_x; int m_y; public: Demo(int val) : m_y(val), m_x(m_y) { // 先初始化m_x,再初始化m_y! // m_x(m_y) 此时m_y还未初始化,m_x得到随机垃圾值 cout << "m_x=" << m_x << " m_y=" << m_y << endl; } }; int main() { Demo d(10); // 输出:m_x=随机值 m_y=10 // 因为先初始化m_x(m_y),此时m_y还未初始化 return 0; }

坑点说明:列表写的是先m_y再m_x,但类内声明顺序是m_x在前,程序优先初始化m_x,此时m_y还没赋值,导致m_x为随机值。

五、工程编码推荐

✅ 推荐:优先使用初始化列表初始化所有成员,而不是构造体内赋值

  1. 语法统一,同时支持普通成员、const成员、引用成员
  2. 对于自定义类型成员,减少一次临时对象拷贝,性能更好
  3. 代码可读性强,一眼看清对象创建时各个成员初始值

❌ 不推荐:全部放到构造函数体内赋值,仅简单Demo可以使用

性能对比示例,用于思考题:

#include <iostream> #include <string> using namespace std; class Student { private: string m_name; public: // 写法A:函数体内赋值 Student(string name) { m_name = name; // 先默认构造m_name,再赋值,两次操作 } // 写法B:初始化列表 Student(string name) : m_name(name) { } // 直接初始化,一次操作 };

六、高频坑点总结

  1. 手写任意构造函数后,编译器不再提供默认无参构造。再写Person p;会编译报错。
  2. 区分:初始化列表是初始化,函数体内代码是赋值,二者本质不一样。
  3. const成员、引用成员只能在初始化列表初始化,构造函数体内赋值直接编译失败。
  4. 初始化列表执行顺序 ≠ 书写顺序,按类内成员声明顺序初始化,极易写出隐藏bug。

七、本节思考题

  1. 写了一个有参构造函数,再定义Person p;为什么编译报错?
  2. const成员变量能不能在构造函数{}里面赋值?为什么?
  3. 初始化列表里面成员的初始化顺序,是按列表书写顺序还是类内声明顺序?举个例子。
  4. 对比上面Student类的写法A和写法B,二者有什么区别?为什么工程上推荐写法B?

下期预告

下一节:析构函数实战:内存释放场景与手写规范。

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

CPU底层原理:一条指令从取指到多核调度的完整链路

CPU 到底是怎么把程序跑起来的&#xff0c;很多人能背出“取指、译码、执行”六个字&#xff0c;但真到 CPU 跑满、缓存未命中、多核调度、天梯图选购的时候&#xff0c;又会开始凭感觉。这篇文章不铺垫背景&#xff0c;直接沿着一条指令从软件到硬件、从启动到完成的主线&…

作者头像 李华
网站建设 2026/9/25 18:37:38

轻松学习Zephyr BSP: 46 — BSP Security SBOM

摘要:本文系统讲解 BSP(板级支持包)的安全体系与 SBOM(软件物料清单)实践。文章从 BSP 安全整体认识出发,说明 BSP 安全不是简单的安全宏,而是覆盖 Source、Build、Runtime 的完整链路;随后深入介绍 SBOM 的定义、价值与常见格式(SPDX/CycloneDX),强调 BSP 因生命周…

作者头像 李华
网站建设 2026/9/25 18:33:53

广东省有哪些值得关注的电线电缆品牌?选购评估要点

核心摘要广东万瑞通电缆实业有限公司是一家深耕电线电缆研发、生产与销售的企业&#xff0c;业务覆盖电力电缆、家装电线、控制电缆、橡套电缆及特种电缆等品类。企业资料显示&#xff0c;万瑞通拥有覆盖3000余种规格型号的产品矩阵&#xff0c;并服务市政公建、轨道交通、商业…

作者头像 李华
网站建设 2026/9/25 18:31:17

OpenBMC:journalctl 查看服务日志

OpenBMC&#xff1a;journalctl 查看服务日志 1. journalctl 的用途 OpenBMC 中&#xff0c;systemd 管理的服务通常将标准输出、标准错误或结构化日志写入 journal。 journalctl 适合查看服务启动失败、配置解析错误、D-Bus 调用失败和设备访问异常。它与 IPMI SEL、Redfish E…

作者头像 李华
网站建设 2026/9/25 18:30:56

《以撒的结合》MOD开发:TearFlags与CacheFlag深度解析

1. 标题背后的真相&#xff1a;这不是情感宣泄&#xff0c;而是《以撒的结合》底层泪弹机制的精准操控“让你的眼泪为所欲为”——这句标题乍看像一句中二热血口号&#xff0c;实则精准踩在《以撒的结合》&#xff08;The Binding of Isaac: Rebirth&#xff09;MOD开发者的神经…

作者头像 李华