拷贝构造函数
class Test
{
public:
Test() = default;
// 普通构造函数
Test(int i) : m_i(i) {}
// 拷贝构造函数
Test(const Test& other)
{
this->m_i = other.m_i;
}
private:
int m_i;
};
拷贝构造函数的调用时机
- 函数的参数为类的对象时
- 函数返回值是类的对象时(产生临时对象)
- 用一个对象去初始化同类的另一个对象时
void func(string str){
}
{
string s1 = "http://c.biancheng.net";
string s2(s1);
string s3 = s1;
string s4 = s1 + " " + s2;
func(s1);
}
s1、s2、s3、s4 以及 func() 的形参 str,都是使用拷贝的方式来初始化的。
class Test
{
public:
Test() = default;
// 普通构造函数
Test(int i) : m_i(i) { }
// 拷贝构造函数
Test(const Test& other) {
cout<<"Test copy function: "<<other.m_i<<endl;
this->m_i = other.m_i;
}
private:
int m_i;
};
Test t1(1); // 构造函数
Test t2(t1); // 拷贝构造
Test t3 = t1; // 拷贝构造
拷贝构造函数的参数必须是自身类型的引用
如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。
只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 C++ 语法的要求。
为什么是 const 引用呢?
拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 const 限制后,这个含义更加明确了。
另外一个原因是,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型。如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了。
默认拷贝构造
对于简单的类,默认拷贝构造函数一般是够用的,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认拷贝构造函数就不能拷贝这些资源,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据
赋值运算符
// 赋值运算符
Test& operator= (const Test& other) {
cout<<"Test operator= function: "<<other.m_i<<endl;
if (this == &other) {
return *this;
}
// 对数据进行深拷贝
m_i = other.m_i;
return *this;
}
Test t1(1); // 构造函数
Test t2(t1); // 拷贝构造
Test t3 = t1; // 拷贝构造
Test t4;
t4 = t1; // 赋值运算符