本篇博客将结合一份完整的 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 代码的基础也是应对面试和实际项目开发的必备技能。希望本篇博客能为你提供一份有价值的参考。
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 代码的基础也是应对面试和实际项目开发的必备技能。希望本篇博客能为你提供一份有价值的参考。