ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C++默认成员函数:拷贝构造与赋值运算符重载详解

C++默认成员函数:拷贝构造与赋值运算符重载详解 1.拷贝构造拷贝构造函数的含义用一个已经存在的同类型对象去初始化一个新对象。拷贝构造是一种特殊的构造函数。拷贝构造函数的定义如下class Date { public: Date(Date d) // 拷贝构造函数 { _year d._year; _month d._month; _day d._day; } private: int _year; int _month; int _day; };拷贝构造函数的特点1.1 拷贝构造函数是构造函数的一个重载1.2 调用时机自定义类型对象进行拷贝行为必须调用拷贝构造所以传值传参和传值返回都会调用拷贝构造完成2.1初始化时调用Date d1(2024, 1, 1); Date d2(d1); // 调用拷贝构造 本质上是 Date( d2, d1) Date d3 d1; // 也调用拷贝构造注意调用时前面的类型(Date)不能省略2.2传值传参时调用void test1(Date x) {} int main() { Date a; test1(a);// 可能调用拷贝构造 return 0; }2.3传值返回时调用Date test2() { Date a; return a;// 可能调用拷贝构造但现代编译器常优化 }1.3 拷贝构造的第一个参数必须是同类型对象的引用不能使用传值传参方式剩下的参数必须有缺省值。如果不使用引用就会造成无限递归当传值传参时会产生临时对象调用拷贝构造-传值传参-拷贝构造-……而引用是别名引用传参不会调用拷贝构造正确写法不修改参数的情况下加const1.4如果没有显式声明拷贝构造函数编译器通常会隐式生成一个。默认行为:对内置类型成员变量的拷贝值拷贝 (浅拷贝一个字节 一个字节地拷贝)对自定义类型成员变量的拷贝调用它的拷贝构造函数class Stack { public: // 构造函数 Stack(int n 4) { _a (int*)malloc(sizeof(int) * n); if (nullptr _a) { perror(malloc申请空间失败); return; } _capacity n; _top 0; } // 析构 ~Stack() { cout ~Stack() endl; if (_a) { free(_a); _a nullptr; _top _capacity 0; } } private: int* _a; size_t _capacity; size_t _top; }; int main() { Stack st1; Stack st2(st1); // 拷贝 return 0; }上述代码没写拷贝构造编译器自动生成按成员逐位拷贝所以当 st2(st1) 执行时相当于st2._a st1._a; // 两个指针指向了同一块堆内存 st2._capacity st1._capacity; st2._top st1._top;后果是st1 和 st2 的 指针 _a 都指向了同一块内存而在析构函数里st2 先析构调用一次 free(_a);st1 后析构再调用一次 free(_a);同一块空间被释放了两次程序会崩溃正确的做法是如果写了析构函数有资源释放那么就意味着必须写拷贝构造函数Stack(const Stack st) { // 1. 为新对象分配独立的内存 _a (int*)malloc(sizeof(int) * st._capacity); if (nullptr _a) { perror(malloc申请空间失败); return; } // 2. 复制数据内容 memcpy(_a, st._a, sizeof(int) * st._top); // 3. 复制其他属性 _top st._top; _capacity st._capacity; }1.5 传引用返回返回的是别名没有拷贝。但要注意返回的对象不能是局部对象2.拷贝赋值 - 赋值运算符重载2.1赋值运算符重载是一个默认成员函数用于将一个已存在对象的值赋给另一个已存在的对象。拷贝构造是将一个对象初始化另一个对象语法类名 operator(const 类名 变量名) { // 赋值逻辑 return *this; }要点返回类型类名。返回引用是为了支持连续赋值如 d1 d2 d3并且避免返回时产生临时对象的拷贝开销。参数:用不用引用都行一般不会引发无限递归。但最好是传引用提高效率。必须定义为成员函数规定不能定义为全局函数调用int main() { Date d1; Date d2(2026,1,1); d1 d2; // 拷贝赋值 - 赋值 Date d3 d2;// 拷贝构造 - 初始化 return 0; }注意区分拷贝构造和拷贝赋值2.2默认生成的赋值运算符重载和拷贝构造类似对内置类型成员变量会进行浅拷贝对自定义类型成员变量会调用它的赋值重载函数2.3拷贝赋值的自赋值问题如果写了 d1 d1或者两个指针指向同一个对象会发生什么如果是深拷贝不加判断的话可能会先把自身的资源释放掉然后再去访问已经释放的内存进行赋值直接导致崩溃class MyString { private: char* data; public: MyString(const char* str ) { data new char[std::strlen(str) 1]; std::strcpy(data, str); } ~MyString() { delete[] data; } // 错误的赋值运算符没有检查自赋值 MyString operator(const MyString other) { delete[] data; // 先释放自己的内存 data new char[std::strlen(other.data) 1]; // 如果 other 就是自己other.data 已悬空 std::strcpy(data, other.data); return *this; } const char* c_str() const { return data; } }; int main() { MyString s(Hello); s s; // 自赋值 std::cout s.c_str() std::endl; // 未定义行为可能崩溃或输出乱码 return 0; }解决方案// 加个判断 if (this ! d) { // 执行深拷贝逻辑 }3.拷贝构造 vs 拷贝赋值 对比总结拷贝构造和拷贝赋值是 C 中两个容易混淆的概念下面从多个维度进行对比对比项拷贝构造拷贝赋值本质构造函数的一种重载用于初始化新对象赋值运算符重载用于赋值已存在的对象调用时机用一个已存在对象初始化一个新对象时如 Date d2(d1) 或 Date d3 d1将一个已存在对象的值赋给另一个已存在对象时如 d1 d2函数形式Date(const Date d)Date operator(const Date d)返回类型无返回值构造函数类名返回引用支持连续赋值参数要求第一个参数必须是同类型对象的引用不能传值否则无限递归传引用或传值均可但推荐传引用提高效率对象状态目标对象是新创建的尚未初始化目标对象已存在可能已有资源需要先释放自赋值问题不存在自赋值问题新对象不可能等于自己需要判断this ! d防止自赋值导致崩溃默认生成未显式声明时编译器隐式生成浅拷贝未显式声明时编译器隐式生成浅拷贝深拷贝场景写了析构函数有资源释放就必须写拷贝构造写了析构函数有资源释放就必须写拷贝赋值典型代码对比int main() { Date d1(2024, 1, 1); Date d2(d1); // 拷贝构造初始化新对象 d2 Date d3 d1; // 拷贝构造初始化新对象 d3注意不是赋值 Date d4(2025, 1, 1); d4 d1; // 拷贝赋值把 d1 的值赋给已存在的 d4 return 0; }记忆口诀拷贝构造看「初始化」——对象是新创建的拷贝赋值看「赋值」——对象已存在只是把值赋过去。
返回列表