昨天晚上在看智能指针的时候,我发现自己连一个拷贝构造函数和赋值构造函数都写不出来,自己就尝试写了一个版本,结果发现错误百出,对于拷贝构造函数和赋值构造函数的理解仅仅停留在理论的方面,而不知其中太多的内涵。
比如我们都知道拷贝构造函数和赋值构造函数最大的不同在于:
拷贝构造是确确实实构造一个新的对象,并给新对象的私有成员赋上参数对象的私有成员的值,新构造的对象和参数对象地址是不一样的,所以如果该类中有一个私有成员是指向堆中某一块内存,如果仅仅对该私有成员进行浅拷贝,那么会出现多个指针指向堆中同一块内存,这是会出现问题,如果那块内存被释放了,就会出现其他指针指向一块被释放的内存,出现未定义的值的问题,如果深拷贝,就不会出现问题,因为深拷贝,不会出现指向堆中同一块内存的问题,因为每一次拷贝,都会开辟新的内存供对象存放其值。
下面是浅拷贝构造函数的代码:
#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; }
运行截图如下:
但是赋值构造函数是将一个参数对象中私有成员赋给一个已经在内存中占据内存的对象的私有成员,赋值构造函数被赋值的对象必须已经在内存中,否则调用的将是拷贝构造函数,当然赋值构造函数也有深拷贝和浅拷贝的问题。当然赋值构造函数必须能够处理自我赋值的问题,因为自我赋值会出现指针指向一个已经释放的内存。还有赋值构造函数必须注意它的函数原型,参数必须是引用类型,返回值也必须是引用类型,否则在传参和返回的时候都会再次调用一次拷贝构造函数。
#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& operator=(const A& a) //记住形参和返回值一定要是引用类型,否则传参和返回时会自动调用拷贝构造函数 { if(this == &a) //为什么需要进行自我赋值判断呢?因为下面要进行释放n的操作,如果是自我赋值,而没有进行判断的话,那么就会出现讲一个释放了的内存赋给一个指针 return *this; if(n != NULL) { delete n; n == NULL; //记住释放完内存将指针赋为NULL } n = new int[10]; memcpy(n, a.n, 10); cout<<"assign constructor is called\n"; return *this; } ~A() { cout<<"destructor is called\n"; delete n;
n = NULL; //记住释放完内存将指针赋为NULL } void get() { cout<<"n[0]: "<<n[0]<<endl; } }; int main() { A* a = new A(); A* b = new 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 operator=(const A a) //传参和返回值设置错误 { if(this == &a) return *this; if(n != NULL) { delete n; n == NULL; } n = new int[10]; memcpy(n, a.n, 10); cout<<"assign constructor is called\n"; return *this; } ~A() { cout<<"destructor is called\n"; delete n; } void get() { cout<<"n[0]: "<<n[0]<<endl; } }; int main() { A* a = new A(); A* b = new A(); *b = *a; delete a; b->get(); while(1) {} return 0; }
运行截图如下:
多了两次的拷贝构造函数的调用和两次析构函数的调用。