C中的构造函数是做什么用的?
摘要:深入理解 C++ 构造函数的本质:从资源管理到性能优化。涵盖默认构造、拷贝构造、移动构造,以及 RAII、explicit、constexpr 等关键机制。
目录核心逻辑构造函数的执行流为什么首选初始化列表?构造函数的分类默认构造函数(Default Constructor)参数化构造函数(Parameterized Constructor)拷贝构造函数(Copy Constructor)移动构造函数(Move Constructor)关键机制与陷阱explicit 关键字:拒绝隐式转换委托构造(Delegating Constructors)构造与虚函数RAII 与构造函数相关关键字控制编译器行为= default= delete(C++11)using(继承构造函数)性能优化noexceptconstexpr(C++11/14)逻辑控制与异常处理explicittry(Function-try block)
本文首发于我的个人博客:Better Mistakes
版权声明:本文为原创文章,转载请附上原文出处链接及本声明。
由于技术迭代较快,文章内容可能随时更新(含勘误及补充)。为了确保您看到的是最新版本,并获得更好的代码阅读体验,请访问:
🍭 原文链接:https://bfmhno3.github.io/note/constructor-in-cpp/](https://bfmhno3.github.io/note/constructor-in-cpp/)
对于 C++ 对象而言,我们认为:对象 = 内存 + 语义(不变量)。
内存:仅仅是电子与硅晶体中状态未知的比特位。
语义:这段内存代表什么含义(是 int、是 char 还是 float),以及它必须满足的条件(“不变量”,Invariant)。
构造函数(Constructor)的本质就是将 “原始、混沌” 的内存强制转换为 “持有特定语义的、合法的对象” 的原子操作过程。
核心逻辑
在 C 语言中,创建一个 struct 通常分为两步:
分配内存(malloc 或栈上声明)
赋值(init 函数或手动赋值)
问题在于:如果在第 1 步和第 2 步之间使用该对象,就会导致灾难(未定义行为)。或者,如果使用者忘记了第 2 步,系统就会处于 “非法状态”。
C++ 引入构造函数就是为了保证:
如果一个对象存在,那么它一定是合法的。
构造函数保证了初始化(Initialization)与定义(Defination)的不可分割性。
构造函数的执行流
当你写下 T object(args); 时,编译器实际执行了以下步骤:
分配内存:在栈或堆上找到一块足够容纳 sizeof(T) 的空间。此时,内存里的数据是随机的(Garbage)。
执行初始化列表(Initialization List):这是真正的初始化时刻。
执行函数体(Function Body):这实际上是后续的计算或赋值操作,而非初始化。
为什么首选初始化列表?
因为 C++ 规定成员变量在进入构造函数体 {} 之前必须完成构建。
Class() : member(value) {} // 直接在内存位置上构造 member
使用初始化列表的成本仅为 1 次构造。
Class() { member = value; }
过程:
调用 member 的默认构造函数(无参)。
调用 member 的赋值运算符 operator=。
在这个过程中的成本为:1 次构造 + 1 次赋值(还可能设计旧内存释放和新内存申请)。
初始化列表不仅是效率优化,对于 const 成员或 reference(引用)成员,它是唯一的初始化方式,因为它们创建后不可修改(不可赋值)。
构造函数的分类
根据对象资源管理的不同需求,构造函数演化出了四种主要形态。我们将用资源所有权的视角来区分它们。
默认构造函数(Default Constructor)
语义:无中生有
形式:T()
视角:当对象被创建但外界未提供任何信息时,对象应处于什么状态?通常是 “空状态” 或 “零状态”
注意:如果类中包含原始指针,编译器生成的默认构造函数不会置空指针(由于 C 的遗留包袱),这会导致悬垂指针。因此现代 C++ 提倡显式定义或使用成员默认初始化(int* p = nullptr;)
参数化构造函数(Parameterized Constructor)
语义:根据蓝图定制
形式:T(args...)
视角:将外部数据约束映射到内部不变量。例如,创建 “圆” 对象,参数是半径。
