结论:

  1. 拷贝构造函数是函数,赋值运算符是运算符的重载;
  2. 拷贝构造函数会生成新的类对象,赋值运算符不会;
  3. 拷贝构造函数是用一个已存在的对象去构造一个不存在的对象;而赋值运算符重载函数是用一个存在的对象去给另一个已存在并初始化过的对象进行赋值;
  4. 若接受返回值的对象已经初始化过,则会调用赋值运算符,且该对象还会调用析构函数,当对象中包含指针时,会使该指针失效,因此需要重载赋值运算符,使用类似深拷贝或移动构造函数的方法赋值,才能避免指针失效。
  5. 如果只有显示的构造函数,系统会提供默认的拷贝构造;
  6. 如果显示提供了拷贝构造,系统就不会提供默认的无参构造了,用户必需显示提供构造函数;
  7. 当既没有显式的构造函数,也没有拷贝构造时,系统才会提供默认的无参构造;
  8. 显示提供拷贝构造就必需显示提供构造函数;
  9. 显示提供赋值运算符重载就必需显示提供拷贝构造;

成员初始化列表

  1. 使用成员变量初始化列表,少了一次调用默认构造函数的过程,提高效率
  2. 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
  3. 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
  4. 成员变量初始化的顺序跟在初始化列表的顺序无关,与变量声明的顺序有关

测试类

class A
{
public:
    A(){
        cout << "default constructor" << endl;
        cout << "adrres: " << this << "\t point x: " << x << "\t y: " << y << endl;
    }
    A(int t){
        x = new int(0);
        y = t;
        cout << "second constructor" << endl;
        cout << "adrres: " << this << "\t point x: " << x << "\t y: " << y << endl;
    }
    A(const A &a){
        cout << "const copy constructor" << endl;
        cout << "adrres: " << this << "\t point x: " << x << "\t y: " << y << endl;
        this->x = a.x;
        this->y = a.y;
    }
    
    A& operator = (const A &a){
        cout << "const assignment constructor" << endl;
        this->x = a.x;
        this->y = a.y;   
        return *this;
    }
    ~A(){
        cout << "destructor delete " << this << endl;
        delete x;
    }
    
public:
    int * x {nullptr};
    int y{0};
};

A f(){
    A ret(3);
    cout << "stack f adrres: " << &ret << "\t point x: " << ret.x << "\t y: " << ret.y << endl;
    return ret;
} // 匿名对象 = ret (此处会调用拷贝构造)

一、拷贝构造

1. 对象需要通过另外一个对象进行初始化

int main()
{
    A a(1);
    A c = a;
    cout << "global adrres: " << &c << "\t point x: " << c.x << "\t y: " << c.y << endl;

    return 0;
}

输出:

Start
second constructor
adrres: 0x7ffdf2cf5cc0	 point x: 0x1e9db20	 y: 1
const copy constructor
adrres: 0x7ffdf2cf5cb0	 point x: 0	 y: 0
global adrres: 0x7ffdf2cf5cb0	 point x: 0x1e9db20	 y: 1
destructor delete 0x7ffdf2cf5cb0
destructor delete 0x7ffdf2cf5cc0
0
Finish

2. 对象通过值传递方式进入函数

void g(A a){
    cout << "stack g adrres: " << &a << "\t point x: " << a.x << "\t y: " << a.y << endl;
}
int main()
{
    A a(1);
    g(a);
    
    return 0;
}

输出:

Start
second constructor
adrres: 0x7ffddc160510	 point x: 0x2448b20	 y: 1
const copy constructor
adrres: 0x7ffddc160520	 point x: 0	 y: 0
stack g adrres: 0x7ffddc160520	 point x: 0x2448b20	 y: 1
destructor delete 0x7ffddc160520
destructor delete 0x7ffddc160510
0
Finish

3. 当对象以值传递的方式从函数返回

  • 函数返回匿名对象时会调用拷贝构造
  • 变量在接收匿名对象时就不会调用拷贝构造了
class A
{
    //注意把拷贝构造设为禁止使用,否则看不到效果
    A(const A &a) = delete;
}
int main()
{
    f();        // 如果没有变量接收匿名对象,编译器就直接回收了,立即执行对象析构 
    A c = f();  // 匿名对象直接转正,就不会调用拷贝构造了
    A d;
    d = f();    // 此处会调用 = 号操作符重载(d = 匿名对象)
                // 然后匿名对象会执行析构,立即销毁
    cout << "global adrres: " << &c << "\t point x: " << c.x << "\t y: " << c.y << endl;
    
    return 0;
}

输出:

Start
prog.cc: In function 'A f()':
prog.cc:40:12: error: use of deleted function 'A::A(const A&)'
   40 |     return ret;
      |            ^~~
prog.cc:20:5: note: declared here
   20 |     A(const A &a) = delete;
      |     ^
1
Finish

正常输出:虽然没有输出调用拷贝构造的信息,但确实有关系

Start
second constructor
adrres: 0x7fff0f7062d0	 point x: 0x127eb20	 y: 3
stack f adrres: 0x7fff0f7062d0	 point x: 0x127eb20	 y: 3
global adrres: 0x7fff0f7062d0	 point x: 0x127eb20	 y: 3
destructor delete 0x7fff0f7062d0
0
Finish

二、赋值构造

  • 注意:拷贝构造不能是 delete

1. 对象直接赋值给另一个对象,且接受值的对象已经初始化过

int main()
{
    A a(1);
    A c;
    c = a;
    cout << "global adrres: " << &c << "\t point x: " << c.x << "\t y: " << c.y << endl;

    return 0;
}

输出:

Start
second constructor
adrres: 0x7ffee27a6fa0	 point x: 0xcdeb20	 y: 1
default constructor
adrres: 0x7ffee27a6f90	 point x: 0	 y: 0
const assignment constructor
global adrres: 0x7ffee27a6f90	 point x: 0xcdeb20	 y: 1
destructor delete 0x7ffee27a6f90
destructor delete 0x7ffee27a6fa0
0
Finish

2. 对象以值传递方式从函数返回,且接受返回值的对象已经初始化过

  • 注意:返回函数调用后,会调用析构,如果对象中包含指针时,会使该指针失效
int main()
{
    A c;
    c = f();
    // 此时调用指针会出错,f()结束,调用对象析构,对象中的指针被 delete 掉了
    // cout << *c.x << endl;
    cout << "global adrres: " << &c << "\t point x: " << c.x << "\t y: " << c.y << endl;
    
    return 0;
}

输出:

Start
default constructor
adrres: 0x7ffd3aed2be0	 point x: 0	 y: 0
second constructor
adrres: 0x7ffd3aed2bf0	 point x: 0x1475b20	 y: 3
stack f adrres: 0x7ffd3aed2bf0	 point x: 0x1475b20	 y: 3
const assignment constructor
destructor delete 0x7ffd3aed2bf0
global adrres: 0x7ffd3aed2be0	 point x: 0x1475b20	 y: 3
destructor delete 0x7ffd3aed2be0
0
Finish