深拷贝、浅拷贝、拷贝构造函数 、移动构造函数
关于拷贝构造、深拷贝、浅拷贝参考/qq_29344757/article/details/76037255
浅拷贝只拷贝指针,不新开辟内存。深拷贝会另外开辟一块内存,内容和拷贝的对象一样。
所谓拷贝构造,传入的参数限定于是同一类之前创建的对象,用它来初始化新建的对象。
拷贝构造主要就是把别的对象的成员变量的值赋值给自己的成员变量。或者说,直接新开辟一段内存,然后把传入的对象的成员变量的值赋值给自己。并不能直接把其他对象直接复制给自己,没有这种用法。
对于传入的参数是例如int类型之类的构造函数,他不叫拷贝构造函数,它只是简单的构造函数。
默认的拷贝构造函数,对于指针变量是浅拷贝,对于非指针变量是深拷贝。这样在析构时会造成double free的错误。所以必须自己定义拷贝构造函数,在里面对指针变量新分配内存后,再把别的对象的指针里面的值赋给它。
默认拷贝赋值函数也是浅拷贝。
所以类的成员变量中有指针变量时,必须对拷贝构造函数和拷贝赋值函数重新定义。
拷贝构造的作用是防止浅拷贝。
因为如果我们不使用深拷贝而使用浅拷贝的话,对象a浅拷贝对象b,当浅拷贝的对象b析构后,b所指向的内存已经释放,那么a所指向的内存也被释放了,当a自己再析构的时候就析构不了了。
还有就是浅拷贝时,任何一个对象对该值进行修改都会影响另一个对象中的值。
默认拷贝构造函数定义举例:
class A{public://默认拷贝构造函数为A(const A& a){tmp1=a.tmp1;//深拷贝,不同对象tmp1的地址不一样ptr=a.ptr;//浅拷贝,因为ptr为指针变量}private:int tmp1;int *ptr;};
我们自己需要重新定义的拷贝构造函数:
A(const A& a){tmp1=a.tmp1;//深拷贝,不同对象tmp1的地址不一样ptr= new int;*ptr=*(a.ptr);//深拷贝,因为他们的ptr的地址不一样了。}
移动构造函数
移动构造函数就是右值引用构造函数。他是为了实现浅拷贝,复用其他对象中的资源(堆内存),延长其他临时对象的生命周期。
A(A&& a):ptr(a.ptr){a.ptr=nullptr;//调用移动构造函数,会先把a对象的指针变量ptr先赋值给自己的指针变量ptr,然后把a.ptr指向空指针,这样a在析构的时候就不会把a.ptr本来指向的内容给释放了。这样自己的ptr指针还是指向那块内存。注意,指针指向的那块内存的值是通过*p=?的方式来修改的,所以修改指针指向并不是修改指针指向的内存的值,不要混淆。}
另外,关于拷贝构造函数和移动构造函数,他们传入的形参都一样,怎么知道调用哪个呢?程序会判断这个形参是不是临时对象,如果是临时对象,就会调用移动构造函数。
注意上面说的传入的形参,也可以是通过类似A a=func()这种使用方式。其中
A func(){A a;return a; //返回一个临时对象。}
所谓拷贝构造,传入的参数限定于是同一类之前创建的对象,用它来初始化新建的对象。
如果类中有指针类型的成员变量,那就必须定义拷贝构造函数,否则你让别的对象的指针成员变量给他赋值,就是浅拷贝,有内存风险。
void test(int a, int b){}test是函数的首地址,他是一个函数,类型是void()。&test表示一个指向函数test这个对象的地址,他是一个指针类型是void(*)()。所以test和&test所代表的地址值是一样的,但是类型不一样。