详解拷贝构造函数&拷贝赋值运算符

参考书籍: c++ primer 5

拷贝构造函数

  • 定义

    • 如果一个构造函数的第一个参数是自身类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
  • 何时发生拷贝初始化(即,调用拷贝构造函数)

    1. =定义变量时。
      • 简记:=定义变量 。
    2. 将一个对象作为实参,传递给一个非引用类型的形参。
      • 简记:拷贝传参。
    3. 从一个返回类型为非引用类型的函数返回一个对象。
      • 简记:拷贝返回。
    4. 用花括号列表初始化一个数组中的元素或一个聚合类中的成员。
      • 简记:花括号列表初始化。

使用的例子:

string a("hello world");

拷贝赋值运算符

先了解一些重载运算符:
重载运算符本质上是函数,其名字由operator关键字后跟运算符组成。
如:operator=

  • 重载赋值运算符的参数表示运算符的运算对象。

  • 某些运算符,包括赋值运算符,必须定义为成员函数

  • 如果一个运算符是成员函数,其左侧对象就绑定到隐式的this参数。

  • 对于二元的运算符,如赋值运算符,其右侧运算对象作为显式参数传递

  • 拷贝赋值运算符规范

    • 为了与内置类型的赋值保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用

标准库通常要求保存在容器中的类型要具有赋值运算符,且其返回值是左侧对象的引用。

和拷贝构造函数类似,拷贝赋值运算符也是用于拷贝。
但是拷贝构造函数强调构造,拷贝复制运算符强调赋值。

为了确保你理解了这一点,给一个例子:

class A {
public:
	A() = default;
	A (int a) : a (a) { cout << "普通构造函数\n"; }
	A (const A &right) : a (right.a) { cout << "拷贝构造函数\n"; }
	int a;
};

int main (void)
{
	A num (99);    // 普通构造函数:直接初始化
	A num1 = 99;    // 普通构造函数:拷贝形式初始化
	num = 99;        // 普通构造函数:赋值,隐式调用了普通构造函数

    A num2(num);     //拷贝构造函数:直接初始化
    A num3 = num;     //拷贝构造函数:拷贝形式初始化
}

这个比较简单,输出即是注释标明的结果。

如果我们为A定义了拷贝赋值运算符,那么结果就会有变:

class A {
public:
	A() = default;
	A (int a) : a (a) { cout << "普通构造函数\n"; }
	A (const A &right) : a (right.a) { cout << "拷贝构造函数\n"; }
	A &operator= (int a) {cout << "拷贝赋值运算符\n"; return *this;}
	int a;
};

int main (void)
{
	A num (99);    // 普通构造函数:直接初始化
	A num1 = 99;    // 普通构造函数:拷贝形式初始化
	num = 99;        // 拷贝赋值运算符:赋值

    A num2(num);     //拷贝构造函数:直接初始化
    A num3 = num;     //拷贝构造函数:拷贝形式初始化
}

关于num=99为什么会走向不同的结果,这是因为没有对应拷贝赋值运算符的num=99会自动调用普通构造函数,构造一个临时的A类,然后再调用编译器合成的拷贝赋值运算符——(它接受一个A的引用)。

class A {
public:
	A() = default;
	A (int a) : a (a) { cout << "普通构造函数\n"; }
	A (const A &right) : a (right.a) { cout << "拷贝构造函数\n"; }
	//A &operator= (int a) {cout << "拷贝赋值运算符\n"; return *this;}
	A &operator=(const A&right){ cout << "拷贝赋值运算符2\n"; return *this; }
	
	int a;
};

int main (void)
{
	A num (99);
	A num1 = 99;
	num1 = 99;
}

这时你会看到有四条输出:

普通构造函数
普通构造函数
普通构造函数
拷贝赋值运算符2

而如果将A &operator=(const A&right)定义为delete,就无法编译通过。

此外,当定义了接受string的拷贝赋值运算符时,这不符合我对预期:

class A {
public:
	A() = default;
	A (int a) : a (a) { cout << "普通构造函数\n"; }
	A (const A &right) : a (right.a) { cout << "拷贝构造函数\n"; }
	
	A &operator= (string a) { cout << "无用的\n"; return *this;};
	//A &operator= (int a) {cout << "拷贝赋值运算符\n"; return *this;}
	//A &operator=(const A&right){ cout << "拷贝赋值运算符2\n"; return *this; }
	
	int a;
};

int main (void)
{
	A num (99);
	A num1 = 99;
	num1 = 99;
	
	A num2 (num);
	A num3 = num;
}

因为合成的拷贝赋值运算符,应该只在一个类未定义拷贝赋值运算符时,这里依然正常工作了,也许是编译器更智能了,我使用-std=c++11

你可能感兴趣的:(c++,c++,算法,数据结构)