- 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。