1.拷贝构造函数的定义:如果一个构造函数的第一个参数是自身类型的引用,且任何额外参数都有默认值。
!拷贝构造函数的第一个参数必须是一个引用类型。
合成拷贝构造函数:
如果我们没有为一个类定义拷贝构造函数,编译器会为我们定义一个。编译器从给定对象中,依次将每个非static成员拷贝到正在创建的对象中。
2.拷贝初始化:
拷贝初始化不仅在我们使用=定义变量时会发生,在下列情况下也会:
- 将一个对象作为实参,传递给非引用类型的形参
- 从一个返回类型为非引用类型的函数,返回一个对象
- 用花括号列表初始化一个数组中的元素,或一个聚合类的成员
拷贝构造函数自己的参数必须是引用类型,如果其参数不是引用类型,则调用永远也不会成功——为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们又必须调用拷贝构造函数,如此无限循环。
拷贝构造函数在用类的一个对象去初始化该类的另一个对象时调用,以下三种情况相当于用一个已存在的对象去初始化新建立的对象,此时,调用拷贝构造函数:
1.当用类的一个对象去初始化该类的另一个对象时。
2.如果函数的形参是类的对象,调用函数时,将对象作为函数实参传递给函数的形参时。
3.如果函数的返回值是类的对象,函数执行完成时,将返回值返回。
注意:
1>拷贝构造函数只是在用一个已存在的对象去初始化新建立的对象时调用,而对象进行赋值时,拷贝函数将不被调用。
2>用一个常量初始化新建立的对象时,调用构造函数,不调用拷贝构造函数
3>建立对象时,构造函数与拷贝构造函数只有一个被调用
4> 当对象作为函数的返回值时需要调用拷贝构造函数,此时C++将从堆中动态建立一个临时对象,将函数返回的对象拷贝给该临时对象,并把该临时对象的地址存储在寄存器里,从而由该临时对象完成函数返回值的传递
3.浅拷贝与深拷贝
默认的拷贝构造函数实现的只能是浅拷贝,即直接将原对象的数据成员值依次复制给新对象中对应的数据成员,并没有为新对象另外分配内存资源。
这样,如果对象的数据成员是指针,两个指针对象实际上指向的是同一块内存空间。
在某些情况下,浅拷贝回带来数据安全方面的隐患。
当类的数据成员中有指针类型时,我们就必须定义一个特定的拷贝构造函数,该拷贝构造函数不仅可以实现原对象和新对象之间数据成员的拷贝,而且可以为新的对象分配单独的内存资源,这就是深拷贝构造函数。
浅拷贝存在的问题:
#include<iostream>
using namespace std;
class Rect
{
public:
Rect()
{
p=new int(100);
}
~Rect()
{
if(p!=NULL);
delete p;
}
private:
int width;
int height;
int *p;
};
int main()
{
Rect rect1;
Rect rect2(rect1);
return 0;
}
程序运行时会发生错误,在销毁对象时,两个对象的构造函数将对同一个内存空间释放两次。
由于合成的默认拷贝构造函数是浅拷贝,在使用rect1复制rect2时,由于执行的是浅拷贝,只是将成员的值进行赋值,这时 rect1.p = rect2.p,也即这两个指针指向了堆里的同一个空间。
所以必须要用深拷贝,自己定义拷贝构造函数:
#include<iostream>
using namespace std;
class Rect
{
public:
Rect()
{
p=new int(100);
}
Rect(const Rect& r)
{
width=r.width;
height=r.height;
p=new int(100);
*p=*(r.p);
}
~Rect()
{
if(p!=NULL)
delete p;
}
private:
int width;
int height;
int *p;
};
3.防止默认拷贝发生
声明一个私有的拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类的对象,编译器会报告错误,从而可以避免按值传递或返回对象。
总结:
当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数
——
即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。
深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
参考博客:http://blog.chinaunix.net/uid-28977986-id-3977861.html