ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## 一.对象的赋值 1. **实质操作** 把对象的每个数据成员的值赋值给运算符左边的同类型对象。 2. **操作对象** 数据成员。 >[warning]不会复制成员函数,因为相同类型的对象共用成员函数的代码。 ## 二.拷贝构造函数 ### 1.作用 + 指导编译器为对象分配空间 + 完成对象的初始化操作 ### 2.特点 + **无名函数** 因为类型名不能作为函数名。 + **不可指定返回类型** 不能指定任何返回类型,即使void也不行。 + **支持成员初始化列表** ### 3.定义和声明 类似于构造函数,但第一个参数必须为对象的引用。 为支持常对象,一般第一个参数设置为常引用。 ```c++ //一般旧对象那里会加上 const ,防止原对象被修改。 //如需多个参数,后面的参数需要加默认值 类名(类名& 旧对象) { //code here } ``` ### 4.调用时机 用 **同类型的对象** 拷贝初始化正在被创建的 **同类型的对象** 时。即: + 使用一个已存在的同类型对象初始化另一个正在被创建同类型对象。 + 从函数传递一个对象(而不是对象的引用)。 + 从函数返回一个对象(而不是对象的引用)。 对于派生类的拷贝构造函数,若类类型成员没有出现在成员初始化列表,则类类型成员执行 **无参构造函数** 。 >[warning] \[17+\]\[17-的编译器可能支持\] 若使用函数返回的对象创建一个新对象(或没有变量接受返回值),则有可能发生复制消除。 ```c++ #include <iostream> using namespace std; class Complex { private: double real_; double imaginary_; public: Complex() : real_(0), imaginary_(0) { cout << "已调用无参构造函数" << endl; } Complex(double real) : real_(real), imaginary_(0) { cout << "已调用普通构造函数" << endl; } Complex(double real, double imaginary) : real_(real), imaginary_(imaginary) { cout << "已调用普通构造函数" << endl; } Complex(const Complex &old) { this->real_ = old.real_; this->imaginary_ = old.imaginary_; cout << "已调用拷贝构造函数" << endl; } double get_real() { return this->real_; } double get_imaginary() { return this->imaginary_; } Complex &set_real(double new_val) { this->real_ = new_val; return *this; } Complex &set_imaginary(double new_val) { this->imaginary_ = new_val; return *this; } }; void pass_a_complex(Complex c) //拷贝初始化,调用拷贝构造函数 { } Complex return_a_complex() { return Complex(10, 10); //直接初始化,普通构造函数 } int main() { Complex a; //默认初始化,调用无参构造函数 Complex b(10, 10); //直接初始化,调用普通构造函数 Complex c = 10; //拷贝初始化,调用普通构造函数 Complex d = c; //拷贝初始化,调用拷贝构造函数 Complex e(a); //拷贝初始化,调用拷贝构造函数 Complex f = return_a_complex(); //拷贝初始化,可能调用两次拷贝构造函数,也可能不调用任何构造函数(复制消除) pass_a_complex(b); //向函数传递对象,调用拷贝构造函数 return_a_complex(); //从函数返回对象,可能会调用拷贝构造函数 return 0; } ``` >[test-gpp] > 已调用无参构造函数 > 已调用普通构造函数 > 已调用普通构造函数 > 已调用拷贝构造函数 > 已调用拷贝构造函数 > 已调用普通构造函数 > 已调用拷贝构造函数 > 已调用普通构造函数 >[test-vc] > 已调用无参构造函数 > 已调用普通构造函数 > 已调用普通构造函数 > 已调用拷贝构造函数 > 已调用拷贝构造函数 > 已调用普通构造函数 > 已调用拷贝构造函数 > 已调用拷贝构造函数 > 已调用拷贝构造函数 > 已调用普通构造函数 > 已调用拷贝构造函数 ### 5.默认拷贝构造函数 如用户未定义拷贝构造函数,则编译器会产生一个默认的拷贝构造函数。在默认的拷贝构造函数中,每个成员直接逐个复制。类类型成员调用 **拷贝构造函数** 。 >当类满足下列条件时,编译器 **不会** 自动创建默认的拷贝构造函数: > >+ 有用户定义的拷贝构造函数 >当类满足下列条件时,编译器会将拷贝构造函数定义为弃置的(`delete`)(C++11之前,这些情况下编译器不会自动创建默认的拷贝构造函数): >+ 非静态类类型数据成员或基类没有拷贝构造函数,或拷贝构造函数不能访问(例如将拷贝构造函数设置为私有)。 >+ 非静态类类型数据成员或基类没有析构函数,或析构函数不能访问(例如将析构函数设置为私有)。 >+ [11+]有用户定义的移动构造函数和移动赋值操作。 ## 三.拷贝赋值操作 ### 1.作用 + 完成对象之间的赋值 ### 2.特点 1. **有名** 2. **有参** 3. **有返回值** ### 3.定义与声明 + **重载赋值运算符** 参数为对象的引用,返回值为自身的引用。 + **只能作为成员函数** + **不允许默认参数** ### 4.调用时机 + 对象赋值(不是对象的初始化) ### 5.默认拷贝赋值操作 >当类满足下列条件时,编译器 **不会** 自动创建默认的拷贝赋值操作: > >+ 有用户定义的拷贝赋值操作 >当类满足下列条件时,编译器会将拷贝构造函数定义为弃置的(`delete`)(C++11之前,这些情况下编译器不会自动创建默认的拷贝构造函数): >+ 非静态类类型数据成员或基类没有拷贝构造函数,或拷贝构造函数不能访问(例如将拷贝赋值操作设置为私有)。 >+ 非静态类类型数据成员或基类没有析构函数,或析构函数不能访问(例如将析构函数设置为私有)。 >+ [11+]有用户定义的移动构造函数和移动赋值操作。 >+ 某个非静态数据成员是引用类型。 >+ 某个非静态数据成员是常量或常量数组。 ## 四.合成的拷贝函数与动态内存分配 - 如果数据成员的值是一个动态分配的地址,那么在对象复制的时候,程序仅仅把地址复制过来,而不会重新创建新的空间。这样,两个对象的某个数据成员会指向同一片内存区域。 **在对象析构的时候,会重复删除同一片动态内存空间** 。 + 解决问题的办法就是自定义拷贝函数。两种设计方法: + **深拷贝法** 复制对象的时候重新创建空间。 + **优点** 简单,每个对象拥有独立的空间。 + **缺点** 内存开销较大,速度慢。(时间复杂度和空间复杂度高,为线性级) + 在C++中,对象的复制一般使用 **深拷贝法** 。 + **浅拷贝法** 设置一个数据成员 `power` ,对象复制的时候,将运算符右边的对象的 `power` 设置为 `false`。析构的时候,若 `power` 为 `false` 则不删除动态空间。 + **优点** 速度快,内存开销小。(时间复杂度和空间复杂度低,为常数级) + **缺点** 不同对象之间占用同一片内存区域,修改那一块区域的值,所有对象获得的数据都会改变。 + 其他的语言如 `python` 和 `JavaScript` 复制一个容器,默认使用浅拷贝法。