
目录1.构造函数(1) 普通构造函数(2) 转移构造函数(3) 资源所有权的转移逻辑(4) 移动语义的关键作用2.拷贝构造函数3.拷贝赋值函数(1) 移动赋值操作符(2) 资源接管逻辑4.析构函数5.类型转换构造函数(1)使用 explicit 关键字禁止隐式转换explicit 的设计目的实际应用建议示例对比总结总结对比注意事项相关问题拷贝构造函数内部调用自身引发递归无限递归的形成机制引用传递如何避免此问题总结1.构造函数(1) 普通构造函数构造函数是类的一种特殊成员函数用于在创建对象时初始化成员变量。构造函数的名字与类名相同不返回任何类型包括void且可以带有参数。当对象被创建时系统会自动调用对应的构造函数。Human(int age 0, const char* name 无名): m_age(age), m_name(name)这是一个带默认参数的构造函数用于初始化m_age和m_name。若未显式定义构造函数编译器将自动生成一个无参构造函数。(2) 转移构造函数在类String中转移构造函数用于高效地将临时对象右值所拥有的资源如动态分配的内存转移到新对象避免不必要的拷贝操作。该机制是 C11 引入的“移动语义”的核心组成部分。String(String that) : m_psz(that.m_psz) { that.m_psz nullptr; std::cout String类的转移构造函数被调用 std::endl; }(3) 资源所有权的转移逻辑m_psz是指向字符数组的指针表示字符串内容。通过初始化列表将that.m_psz的值直接赋给当前对象的m_psz。随后将原对象that的指针置为nullptr防止析构时重复释放同一块内存。(4) 移动语义的关键作用提升性能避免深拷贝大块数据尤其适用于长字符串或频繁传递临时对象的场景。安全性保障通过置空源对象指针确保不会出现双重释放double-free错误。2.拷贝构造函数拷贝构造函数是一种特殊的构造函数用于用一个已存在的对象来初始化新对象。其形式为类名(const 类名 另一个对象);在代码中Human(const Human that): m_age(that.m_age), m_name(that.m_name)该函数在以下情况被调用使用已有对象初始化新对象如Human h3(h2);函数参数以值传递方式接收对象函数返回对象返回值优化除外拷贝构造函数的作用是逐成员复制确保新对象拥有原对象的数据副本。3.拷贝赋值函数拷贝赋值函数是一个成员函数用于将一个对象的内容赋值给另一个已经存在的对象。其形式为类名 operator(const 类名 另一个对象);在代码中Human operator(const Human that)该函数在以下情况被调用对象之间进行赋值操作如h4 h3;赋值表达式的结果作为右值使用该函数需返回当前对象的引用*this以便支持链式赋值如a b c;。内部实现通常涉及对每个成员变量进行赋值操作。(1) 移动赋值操作符移动赋值操作符用于在对象之间高效转移资源避免不必要的拷贝。其核心思想是“接管”源对象的资源所有权并将源对象置为空状态以防止资源重复释放。该函数签名通常为String operator(String that)实例String operator(String that){ coutString类的转移赋值函数被调用endl; delete[] this-m_psz;//释放旧资源 this-m_psz that.m_psz; that.m_psz NULL; return *this; }(2) 资源接管逻辑源对象that的内部指针m_psz被直接赋值给当前对象的m_psz。通过delete[] this-m_psz释放当前对象原有的内存资源确保不会发生内存泄漏。将that.m_psz设置为nullptr防止源对象在后续析构时重复释放已转移的资源。4.析构函数析构函数是类的一种特殊成员函数用于在对象生命周期结束时执行清理工作。其名字为类名前加波浪号~不接受参数也不返回值。例如~Human()虽然代码中未显式定义析构函数但编译器会自动生成一个空的默认析构函数。析构函数的主要作用包括释放动态分配的内存如new分配的关闭文件句柄、网络连接等资源执行必要的清理逻辑当对象超出作用域或被delete释放时析构函数会被自动调用。5.类型转换构造函数类型转换构造函数是一种特殊的构造函数其形式为class 目标类型 { 目标类型 (const 源类型 src) { … } };该构造函数允许使用一个源类型的对象来初始化一个目标类型的对象。当编译器在表达式中遇到从源类型到目标类型的转换时会自动调用此构造函数从而实现隐式类型转换。类型转换构造函数必须满足以下条件该构造函数只能接受一个参数且该参数不能有默认值。该构造函数不能是explicit修饰的除非明确需要禁止隐式转换。构造函数的参数类型应与目标类类型不同以便实现从该参数类型到类类型的隐式转换。例如//类型转换构造函数 #includeiostream #includecstring using namespace std; class Cat{ public: Cat(const char* name):m_name(name){ //【string m_name(name);】 } void talk(){ cout m_name :喵喵 endl; } private: string m_name; friend class Dog;//友元声明 }; class Dog{ public: Dog(const char* name):m_name(name){ //【string m_name(name);】 } Dog(const Cat c):m_name(c.m_name){ //类型转换构造函数(提供转换规则) //【string m_name c.m_name;】 coutDog类的类型转换函数被调用endl; } void talk(){ cout m_name :汪汪 endl; } private: string m_name; }; //以上的代码模拟类的设计者C标准库的类/第三方提供的类/自己设计的类 //---------------------------- //以下的代码模拟类的使用者 int main(void){ Cat smallwhite(小白); smallwhite.talk(); // Dog bigyellow(smallwhite);//定义bigyellow,利用bigyellow.Dog(smallwhite) Dog bigyellow smallwhite;//定义 匿名Dog类对象利用 匿名Dog类对象.Dog(smallwhite) //Dog bigyellow 匿名Dog类对象 bigyellow.talk(); return 0; }(1)使用 explicit 关键字禁止隐式转换若在构造函数前加上explicit则禁止编译器进行隐式类型转换。此时必须显式地使用静态转换如static_cast才能触发转换。示例class 目标类型 { explicit 目标类型 (const 源类型 src) { … } };实例//类型转换构造函数 #includeiostream #includecstring using namespace std; class Cat{ public: explicit Cat(const char* name):m_name(name){ //类型转换构造 //【string m_name(name);】 cout猫类的类型转换构造 endl; } void talk(){ cout m_name :喵喵 endl; } private: string m_name; friend class Dog;//友元声明 }; class Dog{ public: Dog(const char* name):m_name(name){ //【string m_name(name);】 } explicit Dog(const Cat c):m_name(c.m_name){ //类型转换构造函数(提供转换规则) //【string m_name c.m_name;】 coutDog类的类型转换函数被调用endl; } void talk(){ cout m_name :汪汪 endl; } private: string m_name; }; //以上的代码模拟类的设计者C标准库的类/第三方提供的类/自己设计的类 //---------------------------- //以下的代码模拟类的使用者 int main(void){ //Cat smallwhite(小白); //定义smallwhite,利用smallwhite.Cat(小白); // Cat smallwhite 小白; //定义 匿名Cat类对象利用匿名 Cat类对象.Cat(小白); //Cat smallwhite 匿名Cat类对象 Cat smallwhite static_castCat( 小白); //定义 匿名Cat类对象利用匿名 Cat类对象.Cat(小白); //Cat smallwhite 匿名Cat类对象 smallwhite.talk(); // Dog bigyellow(smallwhite);//定义bigyellow,利用bigyellow.Dog(smallwhite) // Dog bigyellow smallwhite;//定义 匿名Dog类对象利用 匿名Dog类对象.Dog(smallwhite) //Dog bigyellow 匿名Dog类对象 Dog bigyellow static_castDog (smallwhite);//定义 匿名Dog类对象利用 匿名Dog类对象.Dog(smallwhite) //Dog bigyellow 匿名Dog类对象 bigyellow.talk(); return 0; }explicit 的设计目的防止意外的类型转换导致程序行为不符合预期。提高代码安全性与可读性。在模板编程、参数传递等场景中避免歧义。实际应用建议当构造函数仅用于类型转换且不希望被隐式调用时应始终使用explicit。特别适用于单参数构造函数尤其是涉及基本类型如int,double的情况。多参数构造函数通常无需explicit因为多参数无法构成单一隐式转换。示例对比// 无 explicit允许隐式转换 class A { public: A(int x) { /* ... */ } }; A a1 10; // 合法隐式转换 A a2 A(10); // 显式构造也合法 // 有 explicit禁止隐式转换 class B { public: explicit B(int x) { /* ... */ } }; B b1 10; // 错误不允许隐式转换 B b2 static_castB(10); // 正确显式转换总结类型转换构造函数支持从源类型到目标类型的隐式转换。使用explicit可以禁用这种隐式行为。必须通过static_cast目标类型(值)等显式方式触发转换。推荐对所有单参数类型转换构造函数使用explicit以提升代码健壮性。总结对比函数类型调用时机参数形式返回类型是否可重载构造函数创建对象时任意参数无是拷贝构造函数用已有对象初始化新对象const T无是拷贝赋值函数已存在对象间赋值const TT是析构函数对象销毁时无无否注意事项若类中包含指针成员或需要管理外部资源必须显式定义拷贝构造函数和拷贝赋值函数否则默认版本可能导致浅拷贝问题如重复释放内存。推荐遵循“三法则”Rule of Three若定义了析构函数、拷贝构造函数或拷贝赋值函数中的任意一个则通常需要定义全部三个。现代 C 中推荐使用移动语义如move构造函数和移动赋值函数提升性能尤其在处理大对象时。相关问题拷贝构造函数内部调用自身引发递归如果在拷贝构造函数中错误地调用了某个函数而该函数的参数是值传递类型且该函数又涉及对当前对象的操作就可能形成间接调用链。例如假设存在如下代码class T { public: T() default; T(const T other) { // 错误示例在此处调用一个以值传递参数的函数 func(other); // 假设 func 接收的是值类型 } void func(T obj) { // 这里参数是值传递 // ... } };当执行T t1; T t2 t1;时会进入拷贝构造函数此时调用func(other)而other是一个T类型对象必须通过值传递传入func函数。于是在func内部会再次尝试复制other即调用拷贝构造函数从而触发新一轮的拷贝构造函数调用。无限递归的形成机制由于每次调用func(T obj)都需要创建一个新的T对象而创建过程又依赖于拷贝构造函数而拷贝构造函数中又调用了func(T obj)这就形成了一个闭环拷贝构造函数 → 调用func(T obj)func(T obj)→ 创建obj副本 → 触发拷贝构造函数新的拷贝构造函数 → 再次调用func(T obj)……无限循环每层递归都会在栈上分配新的局部变量空间最终导致栈溢出stack overflow。引用传递如何避免此问题将函数参数改为引用传递如void func(const T obj); // 传引用不复制此时func不再需要拷贝obj因此不会触发拷贝构造函数也就不会产生递归调用。同样在拷贝构造函数中调用func(other)时由于other是引用无需复制整个调用链得以终止。总结值传递会导致拷贝构造函数被调用。若拷贝构造函数内部调用了接受值传递参数的函数则会再次触发拷贝构造函数。形成调用链拷贝构造函数 → 函数调用 → 拷贝构造函数 → ……无终止地重复造成无限递归与栈溢出。使用引用传递可避免对象复制切断递归路径。深浅 拷贝构造 与 拷贝赋值如果类不提供拷贝构造和拷贝赋值编译器将提供默认的拷贝构造和拷贝赋值而默认的拷贝构造和拷贝赋值函数对于指针型成员变量都是只复制地址而并不是复制地址指向的数据这将导致浅拷贝问题 为了获得完整意义上的对象副本必须自己定义拷贝构造和拷贝赋值针对指针型成员变量做深拷贝相对于拷贝构造拷贝赋值需要做更多的工作* 避免自赋值* 分配新资源* 拷贝新内容* 释放旧资源* 返回自引用1. 无论是拷贝构造还是拷贝赋值其默认实现对任何类型的指针成员都是简单地复制地址因此应尽量避免使用指针型成员变量2. 出于具体原因的考虑确实无法实现完整意义上的拷贝构造和拷贝赋值可将它们私有化以防误用3. 如果为一个类提供了自定义的拷贝构造函数就没有理由不提供相同逻辑的拷贝赋值运算符函数