目录
1.拷贝构造函数
首先,简单介绍一下拷贝构造函数。拷贝构造函数是构造函数的一种,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
拷贝构造函数将创建好(已初始化)的对象作为参数,返回一个新的对象。
如果我们没有定义拷贝构造函数,系统会自动生成一个默认的拷贝构造函数。
拷贝构造函数的一般形式如下所示:
ClassName(const ClassName& obj)
{}
我们呢来看一个实例:
class People
{
public:
//构造函数
People(int age, string name) :_age(age), _name(name)
{}
// 拷贝构造函数
People(const People& obj)
{
_age = obj._age;
_name = obj._name;
cout << "拷贝构造调用" << endl;
}
void Print()
{
cout << _name << ":" << _age<< endl;
}
private:
int _age;
string _name;
};
int main()
{
People xiaoMing(14, "小明");
People xiaoHong(xiaoMing);
xiaoMing.Print();
xiaoHong.Print();
return 0;
}
打印结果如下:
可以发现小明的属性已经拷贝给了小红,那么如果我们删除掉上述代码中的拷贝构造函数,结果会怎么样?答案是:结果是一样的,这是为什么呢,这是因为系统会生成一个默认的拷贝构造函数,来进行对象初始化对象的操作,但是这种拷贝构造函数只能用于浅拷贝,不能用于深拷贝。接下来我们引出浅拷贝和身拷贝。
2.浅拷贝和深拷贝
假设类中的成员变量有一个指针,我们在类的构造函种为这个指针在堆上申请了内存。如果我们用这个类取初始化其他类会发生什么情况?
我们先来看一个实例:
class People
{
public:
//构造函数
People(int age, string name, int size = 3) :_age(age), _name(name),_size(size)
{
_arr = new int[size];
}
~People()
{
delete[] _arr;
}
void Print()
{
cout << _name << ":" << _age<< endl;
}
private:
int _age;
string _name;
int _size;
int* _arr;
};
int main()
{
People xiaoMing(14, "小明");
People xiaoHong(xiaoMing);
return 0;
}
我们在People类中加了两个变量,一个_size,一个int的指针,然后在析构函数种将_arr的内存释放。执行会发现上述代码会报错,这是为什么呢?
如上图,小明首先在堆上有三个字节的大小,通过一个默认的拷贝构造函数(浅拷贝),小红的_arr指针也指向了这块空间,当程序结束的时候,首先调用小明的析构函数释放了这块空间,被释放掉了,小红的析构函数又取释放这块被释放掉的空间,所以程序会报错。
总结一下:
浅拷贝只是复制指向某些对象的指针,并不会对所指内容进行复制。如上:浅拷贝只是把小名的_arr指针指向了小明的_arr指针,并没又进行空间的赋值。当程序结束的时候,不同对象的析构函数会释放同一块内存,报错。
而深拷贝,通过写拷贝构造函数,使堆上的空间也复制一份,当程序结束的时候,不同对象的析构函数会释放的内存虽然内容一样,但是地址不一样,所以不会报错。
在上述的代码种我们增加拷贝构造函数如下之后,程序不会报错。
People(const People& obj)
{
_age = obj._age;
_name = obj._name;
_size = obj._size;
_arr = new int[_size];
cout << "拷贝构造调用" << endl;
}
3.拷贝构造函数的参数能用值传递吗?
右函数的传参过程我们可以得知,如果函数的参数不是指针或者引用的时候,我们在传参的时候,会将实参复制给形参。
如果我们将拷贝构造函数的参数去掉引用,那么在调用拷贝构造函数的时候,首先会申请一个新的对象,然后用传入的实参去初始化这个新的对象,这个时候还会调用到我们的拷贝构造函数,如此层层调用,形成无限的递归。
因此,拷贝构造函数的参数必须用引用或者指针。