C++ 类和对象(下):从构造函数到编译器优化
本篇博客将结合一份完整的 C 代码实例涵盖 Date 类、Stack 类、内部类等详细剖析类和对象的各个核心知识点包括构造函数、初始化列表、拷贝控制、运算符重载、static 成员、友元、内部类、匿名对象以及编译器优化等。同时会补充代码中未直接体现但必须掌握的扩展内容力求做到面面俱到。1. 类的基本结构封装与成员C 类通过class或struct定义将数据成员变量和操作成员函数封装在一起。访问限定符public、protected、private控制外部对成员的访问权限。在提供的代码中Date类是一个典型的日期类包含了年、月、日三个私有成员变量以及公有的构造函数、拷贝构造函数、赋值运算符、析构函数、各种运算符重载等。class Date { private: int _year; int _month; int _day; public: // 各种成员函数 };2. 构造函数与初始化列表2.1 构造函数的重载与默认参数构造函数在对象创建时自动调用用于初始化对象。可以重载多个构造函数也可以使用默认参数来提供灵活的参数传递方式。Date(int year 2026, int month 7, int day 23) { _year year; _month month; _day day; }以上构造函数带有三个默认参数因此可以以多种方式调用Date d1;→ 使用全部默认值 2026/7/23Date d2(2025);→ 年2025月、日使用默认 7/23Date d3(2025, 4);→ 年2025月4日默认 23Date d4(2025,4,24);→ 全部显式指定注意如果用户定义了任何构造函数编译器不再自动生成默认构造函数。但这里提供了全缺省构造函数因此它本身就是默认构造函数不需要参数就能调用。2.2 初始化列表Initializer List初始化列表是在构造函数参数列表之后、函数体之前以冒号:开始后面跟以逗号分隔的“成员变量(初始值)”的列表。它是成员变量真正初始化的地方而函数体中的赋值只是赋值操作而非初始化。为什么必须使用初始化列表有些类型的成员变量必须在定义时初始化不能在构造函数体内赋值引用成员变量int _ref;const成员变量const int _n;没有默认构造函数的类类型成员Time _t;且Time没有默认构造函数上述三种情况必须在初始化列表中显式初始化否则编译报错。class Time { public: Time(int hour) : _hour(hour) {} // 无默认构造 private: int _hour; }; class Date { public: Date(int x, int year 1, int month 1, int day 1) : _year(year) , _month(month) , _day(day) , _t(12) // 必须初始化 , _ref(x) // 引用必须初始化 , _n(1) // const 必须初始化 {} private: int _year; int _month; int _day; Time _t; int _ref; const int _n; };初始化列表的执行顺序按照成员变量在类中的声明顺序进行初始化而不是按照初始化列表中的顺序。这一点容易出错建议保持声明顺序与初始化列表顺序一致。缺省值C11可以在成员变量声明处直接给缺省值这些值会被用于初始化列表如果该成员在初始化列表中未出现。例如int _year 1; int _month 1; int _day 1;如果构造函数初始化列表中没有显式写出_year则编译器会用缺省值 1 来初始化它。初始化列表总结每个构造函数都有初始化列表即使不写编译器也会生成。每个成员变量都会走初始化列表要么显式指定要么使用缺省值要么默认初始化。显式初始化的成员按给定值初始化未显式初始化但有缺省值的用缺省值都没有的内置类型可能未初始化随机值自定义类型调用默认构造函数若无则编译错误。2.3 explicit 关键字与隐式类型转换C 允许通过构造函数进行隐式类型转换如果构造函数只有一个参数或者除第一个参数外都有默认值那么该构造函数可以用于将参数类型隐式转换为类类型。class A { public: A(int a) : _a(a) {} // ... }; A aa 10; // 隐式转换先构造临时 A(10)再拷贝构造 aa编译器通常会优化为直接构造若想禁止这种隐式转换可以在构造函数前加explicitexplicit A(int a) : _a(a) {} A aa 10; // 编译错误代码还展示了 C11 支持的多参数隐式转换使用花括号A aa {1, 2}; // 需要构造函数 A(int, int) const A ref {1, 2}; // 绑定到临时对象3. 拷贝控制拷贝构造、赋值运算符、析构函数3.1 拷贝构造函数拷贝构造函数用于使用一个同类型对象初始化另一个对象。其参数必须是本类类型的引用通常为 const 引用否则会无限递归因为传值本身需要拷贝。Date(const Date d) { _year d._year; _month d._month; _day d._day; }何时调用拷贝构造用一个对象初始化另一个对象Date d2(d1);或Date d2 d1;函数参数按值传递void func(Date d);调用时实参拷贝给形参。函数返回对象按值返回Date func() { Date d; return d; }返回时拷贝给临时对象可能被优化。深拷贝 vs 浅拷贝当类管理动态资源时如Stack类中的_a指针默认的拷贝构造函数只进行浅拷贝指针值复制导致两个对象指向同一块内存析构时 double free。因此必须显式定义拷贝构造进行深拷贝分配新内存并复制内容。Stack(const Stack s) { _a (STDataType*)malloc(sizeof(STDataType) * s._capacity); memcpy(_a, s._a, sizeof(STDataType) * s._top); _top s._top; _capacity s._capacity; }3.2 赋值运算符重载赋值运算符用于两个已经存在的对象之间的赋值。Date operator(const Date d) { if (this ! d) // 防止自赋值 { _year d._year; _month d._month; _day d._day; } return *this; // 返回左侧对象的引用支持连续赋值 }与拷贝构造的区别拷贝构造是“从无到有”赋值是“已有到已有”。3.3 析构函数析构函数在对象生命周期结束时自动调用用于释放资源。名称是~类名无参数无返回值。~Date() { cout ~Date() endl; }对于Date类没有动态资源析构函数可以不写编译器默认的足够。但对于Stack类必须释放_a指向的内存。~Stack() { free(_a); _a nullptr; _top _capacity 0; }析构顺序先构造的后析构与构造顺序相反。在包含对象成员时先执行类自己的析构体然后成员对象按声明顺序的逆序析构。4. 运算符重载C 允许为自定义类型重载运算符使对象支持与内置类型相似的语法。4.1 比较运算符代码中实现了、!、、、、。其中和作为基础其他复用它们。bool operator(const Date d) { return _year d._year _month d._month _day d._day; } bool operator!(const Date d) { return !(*this d); } bool operator(const Date d) { /* 依次比较年月日 */ } bool operator(const Date d) { return *this d || *this d; } bool operator(const Date d) { return !(*this d); } bool operator(const Date d) { return !(*this d); }注意比较运算符通常定义为类的成员函数也可以定义为友元全局函数。4.2 算术运算符、-、、-为了支持日期加减天数实现了operator、operator-、operator、operator-。它们的实现利用了辅助函数GetMonthDay来获取每月的天数考虑闰年。 实现先检查负数天数转换为减法否则将天数加到_day然后循环进位。Date operator(int day) { if (day 0) return *this - -day; _day day; while (_day GetMonthDay(_year, _month)) { _day - GetMonthDay(_year, _month); _month; if (_month 13) { _month 1; _year; } } return *this; } 实现利用拷贝构造一个临时对象对其调用然后返回临时对象拷贝或移动。Date operator(int day) { Date tmp(*this); tmp day; return tmp; }类似地-和-也有对应实现。4.3 自增自减运算符前置与后置前置d和d有区别后置需要带一个哑元参数int来区分。// 前置 Date operator() { *this 1; return *this; } // 后置 Date operator(int) // int 参数无实际用途仅用于区分 { Date tmp(*this); *this 1; return tmp; }类似实现--前置和后置。4.4 流插入和提取运算符 和 由于和的左操作数是ostream/istream对象不能作为成员函数成员函数的左操作数是类对象本身所以必须定义为全局函数。为了访问私有成员声明为友元。friend ostream operator(ostream out, const Date d); friend istream operator(istream in, Date d);实现时直接访问私有变量。operator还包含循环验证日期合法性。ostream operator(ostream out, const Date d) { out d._year 年 d._month 月 d._day 日; return out; } istream operator(istream in, Date d) { // 输入并校验 return in; }5. static 成员静态成员5.1 静态成员变量用static修饰的成员变量属于类不属于某个对象。所有对象共享同一个静态成员变量。它存储在静态存储区必须在类外单独定义初始化。class A { private: static int _scount; // 声明 }; int A::_scount 0; // 定义并初始化只能在类外静态成员变量不能在类内声明时给缺省值因为它不是某个对象的成员不参与构造初始化列表。5.2 静态成员函数静态成员函数没有this指针因此只能访问静态成员变量/函数不能访问非静态成员。static int GetACount() { return _scount; }调用方式A::GetACount()或通过对象a.GetACount()但对象只是提供了类域本质上还是类级别的调用。应用场景计数对象个数、单例模式等。代码中利用静态成员和内部类实现了“求 12...n”的巧妙解法利用构造函数累加。class Sum { public: Sum() { _ret _i; _i; } static int GetRet() { return _ret; } private: static int _i; static int _ret; }; int Sum::_i 1; int Sum::_ret 0; // 然后在 Solution 中创建 Sum 数组触发 n 次构造函数完成累加。6. 友元friend友元提供了一种打破封装的机制允许外部函数或类访问私有成员。但应谨慎使用以免破坏封装性。6.1 友元函数在类内声明一个全局函数为友元该函数即可访问该类的私有成员。class A { friend void func(const A, const B); // 声明 func 为友元 private: int _a1; };6.2 友元类若 A 类声明 B 类为友元则 B 的所有成员函数都可以访问 A 的私有成员。class A { friend class B; // B 是 A 的友元 private: int _a; }; class B { void accessA(const A a) { cout a._a; } // 合法 };友元关系是单向的A 是 B 的友元不代表 B 是 A 的友元且不可传递。7. 内部类Nested Class内部类是定义在另一个类内部的类。它是一个独立的类只是受到外部类的类域和访问限定符的限制。外部类定义的对象中不包含内部类的对象内部类只是类型定义。特性内部类默认是外部类的友元可以访问外部类的私有成员。内部类可以访问外部类的静态成员无论它们是否为私有。内部类通常在外部类的作用域内使用可以通过外部类::内部类创建对象。示例class A { private: static int _k; public: class B { // 默认是 A 的友元 public: void foo(const A a) { cout _k; // 访问外部类的静态私有成员 cout a._h; // 访问外部类的非静态私有成员因为友元 } }; };内部类常用于实现“专属类”如牛客网的“求 1...n”解法中将Sum放在Solution内部作为私有内部类外界无法使用。8. 匿名对象临时对象匿名对象是用类型名加上参数列表直接创建的对象没有名称。它的生命周期只在当前表达式或语句结束时立即析构。A(); // 调用默认构造函数创建匿名对象 A(10); // 调用带参数的构造用途当只需要临时使用一个对象时避免命名。例如传递参数给函数Solution().Sum_Solution(10); // 创建匿名 Solution 对象并调用成员函数调用后立即销毁或者配合容器、算法等如sort中的比较函数对象sort(vec.begin(), vec.end(), greaterint()); // greaterint() 是匿名对象9. 对象拷贝时的编译器优化Copy Elision / RVO / NRVO现代 C 编译器会尽量省略不必要的拷贝以提高效率。这体现在传参和返回值的优化上。情形一传值传参用已存在的对象传参会调用拷贝构造一般不会优化因为参数已经存在。用临时对象传参如f1(A(1))编译器会将构造临时对象和拷贝构造形参合并为直接在形参位置构造从而省去一次拷贝。用字面量隐式转换f1(1)也是构造拷贝优化为直接构造。情形二返回值优化RVO当函数返回一个局部对象时编译器会直接在该局部对象的内存位置上构造返回值而不是先构造局部对象再拷贝到返回值临时对象。更激进的情况下如 MSVC 2022甚至可以将局部对象构造与外部接收对象的构造合并直接在外层对象的存储空间上构造省去多次拷贝。示例A f2() { A aa; return aa; // 可能优化为直接构造返回值NRVO } A aa2 f2(); // 可能只调用一次构造aa2 直接在函数内构造为了查看优化效果可以使用g -fno-elide-constructors关闭优化但实际项目中应信任编译器优化。10. 补充知识C11 后的扩展虽然本代码主要基于 C98/03 特性但现代 C 还增加了许多重要内容10.1 移动构造函数与移动赋值为了解决深拷贝带来的性能开销C11 引入了移动语义。移动构造和移动赋值通过窃取资源而非复制适用于临时对象右值。class Stack { public: Stack(Stack s) noexcept : _a(s._a), _capacity(s._capacity), _top(s._top) { s._a nullptr; s._capacity s._top 0; } Stack operator(Stack s) noexcept { /* 类似 */ } };10.2 default 和 deletedefault显式要求编译器生成默认的拷贝构造、赋值、析构等。delete禁止拷贝或赋值如Stack(const Stack) delete;。10.3 委托构造函数一个构造函数可以调用同一类的另一个构造函数减少重复代码。Date(int year, int month, int day) : _year(year), _month(month), _day(day) {} Date() : Date(2026, 7, 23) {} // 委托给上面的构造函数总结通过这份详尽的代码解析我们系统地回顾了 C 类和对象的各个重要方面构造函数缺省参数、初始化列表、explicit拷贝控制构造、赋值、析构运算符重载成员和友元static 成员、友元、内部类匿名对象和编译器优化同时我们补充了现代 C 的移动语义、default/delete 等新特性使知识体系更加完整。理解这些细节是编写高质量 C 代码的基础也是应对面试和实际项目开发的必备技能。希望本篇博客能为你提供一份有价值的参考。

相关新闻

“采样步数=质量”是最大误区!基于127组消融实验揭示最优步数-质量-时延黄金三角

“采样步数=质量”是最大误区!基于127组消融实验揭示最优步数-质量-时延黄金三角

更多请点击: https://codechina.net 第一章:扩散模型采样步数的本质认知与误区辨析 采样步数(sampling steps)并非单纯控制生成速度的“超参开关”,而是直接决定反向扩散轨迹在隐空间中离散化逼近连续SDE/ODE解的精度…

2026/10/19 0:49:07 阅读更多 →
全栈技术助力城市生命线安全防线构筑

全栈技术助力城市生命线安全防线构筑

城市基础设施的安全运行,直接关系到千万市民的生命财产安全和城市的正常运转。随着城镇化进程持续深入,地下管网老化、极端天气频发、各类风险交织叠加等挑战日益凸显,传统的被动式、碎片化管理模式已难以应对复杂多变的安全形势。在这一背景…

2026/10/18 20:38:19 阅读更多 →
从“打卡式管理”到“意图感知办公”:AI混合办公管理的范式跃迁(含2023-2024全球TOP10案例拆解)

从“打卡式管理”到“意图感知办公”:AI混合办公管理的范式跃迁(含2023-2024全球TOP10案例拆解)

更多请点击: https://kaifayun.com 第一章:从“打卡式管理”到“意图感知办公”:AI混合办公管理的范式跃迁 传统考勤系统依赖固定时间点的签到/签退动作,将员工行为简化为二进制状态(在线/离线、在岗/缺勤&#xff09…

2026/10/18 20:10:57 阅读更多 →

最新新闻

大规模MIMO信号检测器MATLAB实战对比:MMSE/TASER/LAMA/ADMIN/KBEST

大规模MIMO信号检测器MATLAB实战对比:MMSE/TASER/LAMA/ADMIN/KBEST

简介:本资源是一套面向通信工程专业学生、科研人员及MATLAB算法开发者的大型MIMO系统信号检测算法仿真代码包,聚焦于解决高维信道下低复杂度、高性能信号恢复这一核心问题。压缩包共22个文件,以20个MATLAB函数(.m)为主…

2026/10/19 0:49:07 阅读更多 →
Win10下Charles抓包实战:从解密HTTPS到手机App调试

Win10下Charles抓包实战:从解密HTTPS到手机App调试

实习第一天,带我的导师丢过来一句话:“把电脑收拾利索,把Charles装上,等会儿联调要抓包看登录接口。”我那时候以为Charles是个什么前端框架,等装完打开才发现,它就是一台架在本机上的HTTP代理服务器&#…

2026/10/19 0:49:07 阅读更多 →
Java课程设计打砖块小游戏:Swing+面向对象实战

Java课程设计打砖块小游戏:Swing+面向对象实战

简介:本资源是一套面向高校Java初学者与课程设计实践者的综合性小游戏开发案例集,涵盖15个完整可运行的Java桌面应用项目,如贪吃蛇、扫雷、俄罗斯方块、计算器、记事本等,适用于《Java程序设计》《面向对象编程》等课程的课程设计…

2026/10/19 0:49:07 阅读更多 →
Fiddler汉化版实战指南:HTTPS抓包、证书配置与安全审计

Fiddler汉化版实战指南:HTTPS抓包、证书配置与安全审计

简介:本资源是一套面向网络安全初学者与逆向分析爱好者的抓包与反抓包实战工具集,聚焦HTTP/HTTPS流量捕获、证书信任配置及本地调试防护等典型场景。压缩包内含95个文件,主体为13个可执行程序(含Fiddler主程序、证书生成与环回启用…

2026/10/19 0:49:07 阅读更多 →
C#调用MATLAB绘图:COM与Engine双路线实战指南

C#调用MATLAB绘图:COM与Engine双路线实战指南

简介:这份资源面向需要在C#项目中实现复杂图形绘制的开发者,尤其是希望借助MATLAB强大可视化能力、又不想放弃C#工程化优势的中高级程序员。内容围绕通过MATLAB Compiler SDK将M文件编译为.NET库,再在C#中调用MATLAB函数完成三维曲面绘制这一…

2026/10/19 0:49:07 阅读更多 →
abaqus2021+vs2018+intel oneAPI2022关联程序:把环境变量与编译器路径改到 TaoToken 统一通道的实操大纲

abaqus2021+vs2018+intel oneAPI2022关联程序:把环境变量与编译器路径改到 TaoToken 统一通道的实操大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:48:07 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →