深拷贝与浅拷贝
拷贝构造是确确实实构造一个新的对象,并给新对象的私有成员赋上参数对象的私有成员的值,新构造的对象和参数对象地址是不一样的,所以如果该类中有一个私有成员是指向堆中某一块内存,如果仅仅对该私有成员进行浅拷贝,那么会出现多个指针指向堆中同一块内存,这是会出现问题,如果那块内存被释放了,就会出现其他指针指向一块被释放的内存,出现未定义的值的问题,如果深拷贝,就不会出现问题,因为深拷贝,不会出现指向堆中同一块内存的问题,因为每一次拷贝,都会开辟新的内存供对象存放其值。
下面是浅拷贝构造函数的代码:
#include <iostream> using namespace std; class A { private: int* n; public: A() { n = new int[10]; n[0] = 1; cout<<"constructor is called\n"; } A(const A& a) { n = a.n; cout<<"copy constructor is called\n"; } ~A() { cout<<"destructor is called\n"; delete n; } void get() { cout<<"n[0]: "<<n[0]<<endl; } }; int main() { A* a = new A(); A b = *a; delete a; b.get(); return 0; }
运行结果如下:

下面是深拷贝构造函数的代码:
#include <iostream> #include <string.h> using namespace std; class A { private: int* n; public: A() { n = new int[10]; n[0] = 1; cout<<"constructor is called\n"; } A(const A& a) { n = new int[10]; memcpy(n, a.n, 10); //通过按字节拷贝,将堆中一块内存存储到另一块内存 cout<<"copy constructor is called\n"; } ~A() { cout<<"destructor is called\n"; delete n; } void get() { cout<<"n[0]: "<<n[0]<<endl; } }; int main() { A* a = new A(); A b = *a; delete a; b.get(); return 0; }
输出:

赋值运算符是将一个参数对象中私有成员赋给一个已经在内存中占据内存的对象的私有成员,赋值函数被赋值的对象必须已经在内存中,否则调用的将是拷贝构造函数,当然赋值运算符也有深拷贝和浅拷贝的问题。
下面是赋值运算符的浅拷贝例子:
class Name { public: Name(const char *pname) { size = strlen(pname); pName = (char *)malloc(size + 1); strcpy(pName, pname); } Name(Name &obj) { //用obj来初始化自己 pName = (char *)malloc(obj.size + 1); strcpy(pName, obj.pName); size = obj.size; } ~Name() { cout<<"开始析构"<<endl; if (pName!=NULL) { free(pName); pName = NULL; size = 0; } } void operator=(Name &obj3) { //用obj3来=自己 pName = obj3.pName; size = obj3.size; } private: char *pName; int size; }; void playObj() { Name obj2("obj2..."); Name obj3("obj3..."); //重载=号操作符 obj2 = obj3; //=号操作 } void main() { playObj(); system("pause"); }
这是个十分糟糕的写法,不仅是浅拷贝而且出现了内存泄漏。
下面是修正过的赋值运算符的深拷贝写法(而且避免了内存泄漏)
class Name { public: Name(const char *pname) { size = strlen(pname); pName = (char *)malloc(size + 1); strcpy(pName, pname); } Name(Name &obj) { //用obj来初始化自己 pName = (char *)malloc(obj.size + 1); strcpy(pName, obj.pName); size = obj.size; } ~Name() { cout<<"开始析构"<<endl; if (pName!=NULL) { free(pName); pName = NULL; size = 0; } } void operator=(Name &obj3) { if (pName != NULL) { free(pName); pName = NULL; size = 0; } cout<<"测试有没有调用我。。。。"<<endl; //用obj3来=自己 pName = (char *)malloc(obj3.size + 1); strcpy(pName, obj3.pName); size = obj3.size; } protected: private: char *pName; int size; }; //对象的初始化 和 对象之间=号操作是两个不同的概念 void playObj() { Name obj1("obj1....."); Name obj2 = obj1; //obj2创建并初始化 Name obj3("obj3..."); //重载=号操作符 obj2 = obj3; //=号操作 cout<<"业务操作。。。5000"<<endl; } void main() { playObj(); system("pause"); }
拷贝构造函数和赋值运算符的内存泄漏问题:
拷贝构造函数的内存泄漏出现在:显示的将拷贝构造函数写成深拷贝的方式,要在赋值之前检查一下自己的指针对象是否已经在堆上分配内存了,否则,再次在堆上申请内存,之前 申请的空间就会无法释放,从而出现内存泄漏。还要注意在free空间后要将指针设置为NULL,要避免野指针(,因为free后该指针指向了一块垃圾 内存);
不显示的写拷贝构造函数,编译器会自动调用默认的拷贝构造函数(但是是浅拷贝)。会出现单纯的将两个指针指向了同一块堆空间,容易出 现重复释放内存。

浙公网安备 33010602011771号