1. 什么是拷贝构造函数?
普通的构造函数其功能一般是对成员变量的初始化,通常我们会将一个对象的值赋值给新生成的对象但是类的结构相对比较复杂而拷贝构造函数正好解决了这一问题。拷贝构造函数首先是一个构造函数,所以它的函数名便是类名而其功能就是整体的给类中的成员变量进行赋值,而需要注意的是其参数一定要使用引用,后面会详细介绍。
1. 浅拷贝和深拷贝的区别
浅拷贝:系统会自动生成浅拷贝构造函数,浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
深拷贝:与浅拷贝不同的是它由用户来定义在堆中为所需要的成员变量开辟内存,我们在类中通常会遇到需要在堆中开辟内存的成员变量(如指向堆空间的指针)并且两个对象都拥有同一个资源,对象析构时,该资源将经历两次资源返还,此时必须自定义深拷贝构造函数,为创建的对象分配堆空间,否则会出现动态分配的指针变量悬空的情况(悬空指针:先赋值后被销毁,野指针:未被初始化的指针)
3. 它什么时机被调用?
一般有以下三种情况
(1) 一个对象作为函数参数,以值传递的方式传入函数体
(2) 一个对象作为函数返回值,以值传递的方式从函数返回
(3) 一个对象用于给另外一个对象进行初始化(常称为赋值初始化)
#include<iostream>
using namespace std;
class A
{
public:
A() //默认构造函数
{
cout<<"A()"<<endl;
}
A(int a):ma(a) //带一个参数的构造函数
{
cout<<"A(int a)"<<endl;
}
A(const A& rhs) //拷贝构造函数
{
cout<<"A(const A& rhs)"<<endl;
ma = rhs.ma;
}
~A() //析构函数
{
cout<<"~A()"<<endl;
}
private:
int ma;
};
void B(A b)
{
cout<<"B(A b)"<<endl;
}
A C(A c)
{
cout<<"C(A c)"<<endl;
return c;
}
int main()
{
A x;
A y = x;
B(x);
C(x);
}
B(x);在传参时调用了拷贝构造函数,当函数结束时生命周期结束调用析构函数调用属于(1)
由执行结果可以看出来在C(x);代码执行时先将x传入带有类的参数对象这时会调用一次拷贝构造函数,而return 带值传递时又会调用一次拷贝构造函数,调用属于(1),(2)
A y = x;进行对象间的赋值初始化,调用拷贝构造函数属于(3)
4.为什么拷贝构造函数的参数一定要使用引用?
我们先来看看不使用引用的情况
有上图可以看到不使用引用的话会使程序陷入无限循环中。这个在编译器中是直接报语法错误的。
而当
const A& rhs = x;
可以发现给x起了一个别名可以避免循环递归,所以拷贝构造函数的参数一定要使用引用。