一、深拷贝与浅拷贝
浅拷贝又称为引用拷贝,就是拷贝之后,共用同一个实体,只是引用名不同了,对其所做的改变都将改变原来的值。
深拷贝又称为值拷贝,拷贝之后,有不同的名字,不同的内存空间,对其所做的改变都不会改变原来的值。
主要区别:在拷贝指针的时候,浅拷贝只是把指针拷贝一份,指针所指的内容不拷贝,拷贝后的结果是,两个不同的指针,但是指向同一块内存地址,这样就造成了在调用析构函数进行析构的时候,对同一块内存会析构两次,从而造成程序崩溃,内存泄露。
可以举例来理解,比如原函数中自定义的构造函数中,存在通过new进行动态分配的数据成员(new返回的是一个指针,指向新分配的内存,也就是拷贝时存在指针的拷贝的情况),而如果原函数中没有自定义的拷贝构造函数(内含对动态数据成员的重新动态分配),此时系统就会调用默认的构造函数,也就是只拷贝了指针,而该拷贝后的指针仍然指向原分配的内存。
浅拷贝,不只是把指针拷贝一份,还拷贝指针所指向的内容,拷贝后的结果是不同的两个指针,分别指向两个不同的内存。
所以在对象中存在指针或者引用的拷贝时,存在动态成员时,存在堆(比如new这种动态分配时),文件,系统资源时,一定要用深拷贝,避免浅拷贝,而避免浅拷贝是通过,添加自定义的拷贝构造函数,并且在自定义的拷贝构造函数中必须对动态成员,重新进行内存的动态分配。
会调用拷贝构造函数的情况:
1 类的对象作为函数的参数进行传值时
比如: Point A; func(A);//Point是一个类,A是这个类的对象,在此可以看出func函数的形参是一个类的引用。
2 类的对象以传值的方式作为函数的返回对象的时候
比如:Point func();
3 类的对象给类的对象赋值时
比如:Point A; Point B=A;
以上三种情况都会调用拷贝构造函数,如果没有自定义的拷贝构造函数,系统就会调用默认的拷贝构造函数。
需要我们自定义拷贝构造函数的情况:
1当自定义的构造函数中含有对静态数据成员的处理时。
比如:类Point中的数据成员是:static int count; int a; int b;
自定义的构造函数是:Point(){ count++;}
则系统默认生成的拷贝构造函数:
Point(const Point &p){ a=p.a; b=p.b; }//没有对count自增的拷贝处理
此时本来输出的count为类的对象的个数,这是就不对了。
Point A; Point B=A;此时count本来应该输出2的,但是拷贝的时候没有自增,所以还是1;
需要我们自己定义的拷贝构造函数是:
Point(const Point &p){ a=p.a; b=p.b; count++;}
2 在存在动态成员时,避免浅拷贝,也就是当自定义的构造函数中存在动态成员时,必须自定义拷贝构造函数对动态成员进行重新动态内存分配
比如:类Point中的数据成员有:int a; int b;
类Point中的构造函数如下:Point(){ m=new int(50);}
Point(const Point &p){ a=p.a; b=p.b; *m=*(p.m); }//没有对m重新进行内存动态分配
需要我们自定义的拷贝构造函数是:
Point(const Point &p){ a=p.a; b=p.b; m=int new(50); *m=*(p.m);}
如何避免对默认构造函数的调用,就是在函数中声明一个私有的拷贝构造函数,不必实现。这样以上情况发生的时候,编译就会报错提醒我们。
比如:Point(const Point &p){ }
关于构造函数的一些疑惑:
1一个函数中可以存在多个拷贝构造函数,比如一个const的一个非const的
Point(const Point &);
Point(Point &);
2如何判断是否为拷贝构造函数:构造函数的第一个参数是该类的一个对象的引用,没有其他参数或者其他参数有默认值。
比如
Point(Point &);
Point(Point &,int a=0);
3 拷贝构造函数可以调用类中的private成员。