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...) 视角:将外部数据约束映射到内部不变量。例如,创建 “圆” 对象,参数是半径。
阅读全文