- A+
代码举例
#include
using namespace std;
static int objectCount = 0;
class CExample
{
public:
//构造函数
CExample()
{
objectCount++;
print("constructor is called\n");
}
//拷贝构造函数//使用引用,不可通过引用修改变量的值
CExample(const CExample & c)
{
objectCount++;
print("copy constructor is called\n");
}
//析构函数
~CExample()
{
objectCount--;
print("destructor is called\n");
}
void print(const string& msg = "")
{
if(msg.size()!=0) cout<<msg<<":";
cout<<"objectCount = "<<objectCount <<endl;
}
};
CExample f(CExample c)
{
cout<<"begin of f"<<endl;
c.print("x argument inside f()");
cout<<"end of f"<<endl;
return c;
}
int main()
{
CExample h;
h.print("after construction of h");
CExample h2 = f(h);
h.print("after call to f()")
return 0;
}
运行结果: constructor is called:objectCount = 1 after construction of h:objectCount = 1 copy constructor is called:objectCount = 2 begin of f x argument inside f():objectCount = 2 end of f copy constructor is called:objectCount = 3 destructor is called:objectCount = 2 after call to f() destructor is called:objectCount = 1 destructor is called:objectCount = 0
过程解析:
CExample(const CExample& C) 就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。如果我们不定义这个拷贝构造函数,那么编译器会自动生成该拷贝构造函数。
运行CExample h2 = f(h)时候
进入f()函数前:
- 生成临时变量c
- 调用拷贝构造函数将h赋给c
return返回时候:
- 先会产生一个临时变量,就叫XXXX吧
- 然后调用拷贝构造函数把c的值给XXXX。整个这两个步骤有点像:CExample XXXX(c)
- 在函数执行到最后先析构c局部变量
- 等main()执行完后再析构掉XXXX对象
- ps:这里的临时变量的拷贝构造过程可能因为编译器优化而去掉,所以上面程序在f(h)return的时候,其实只是用f(h)结果初始化h2的时候发生了拷贝构造,return过程的拷贝构造被去掉了。
系统默认的拷贝构造函数会拷贝对象的每一个成员变量,如果成员变量是int,将会把一个该值拷贝给一个int变量;如果成员变量是一个对象,那么会调用该类的拷贝构造函数来拷贝数据,所以如果成员变量是对象,这种操作会递归下去。拷贝构造是成员级别的拷贝,而不是字节级别的,如果拷贝中只有基本类型,没有对象,那么,过程其实就是字节拷贝。
如果成员里面有指针,那么就会拷贝指针,两个指针将会指向同一个内存。
默认拷贝构造函数执行的是浅拷贝
深拷贝和浅拷贝

所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了。深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。这里,Person在进行拷贝构造的时候,name指针只会进行浅拷贝,因为指针其实就是int类型的数据,是个简单的int复制操作,那么两个Person对象的name将会指向同一个内存,如下图左;如果对内存进行析构,将会对同一个内存析构两次而报错。因此,我们需要深拷贝,如下图右,两个Person对象的name指向两个不同的内存,内存中存储的值一样。

如果改成深拷贝,则需要重写拷贝构造函数,为拷贝对象的name重新分配内存空间,并将被拷贝对象的name值赋给拷贝对象的name。

什么时候发生拷贝构造
(1)函数调用时,传实参给形参

(2)初始化

(3)函数return的时候,但这个时候并不一定会发生,编译器会出于效率对不必要的拷贝构造过程进行去除。
建议
每写一个类,都自己编写拷贝构造函数、构造函数、析构函数。
如果你不希望你的对象被拷贝,那你可以把拷贝构造函数声明为private。


