1 拷贝构造函数
它是一种特殊的构造函数,由编译器调用来完成一些基于同一类的其他对象的构件及初始化。
1.1 拷贝函数的调用场景:
1、值传递传递函数体
2、值传递从函数体返回(返回匿名对象)
3、用一个对象初始化另一个对象
1.2 举例说明
1、值传递传递函数体
#include <iostream>
using namespace std;
public:
int a;
Test(int x){
a=x;
}
Test( Test &test){
cout<<"copy"<<endl;
a=test.a;
}
};
void fun1(Test test){ //值传递传入函数体
cout<<"run fun1"<<endl;
}
2、值传递从函数体返回(返回匿名对象)
#include <iostream>
using namespace std;
class Test{
public:
int a;
Test(int x){
a=x;
}
Test( Test &test){
cout<<"copy"<<endl;
a=test.a;
}
};
Test fun2(){
Test t(2);
cout<<"run fun2"<<endl;
return t; //返回匿名对象、复杂类型(值传递从函数体返回)
}
int main() {
Test t3 = fun2();
cout<<"fun2"<<endl;
return 0;
}
3、用一个对象初始化另一个对象
#include <iostream>
using namespace std;
class Test{
public:
int a;
Test(int x){
a=x;
}
Test( Test &test){
cout<<"copy"<<endl;
a=test.a;
}
};
int main() {
Test t1(1);
Test t2=t1; //用一个对象初始化另外一个对象
return 0;
}
2 编译器与默认的拷贝构造函数
(1)如果用户没有自定义拷贝构造函数,并且代码中用到了拷贝构造函数,那么编译器会自动生成默认的拷贝构造函数。
(2)但如果用户定义了拷贝构造函数,编译器就不会再生成拷贝构造函数了。
(3)如果用户之定义了一个构造函数,但不是拷贝构造函数,且代码中用到了拷贝构造函数,纳闷编译器还是会生成默认的拷贝构造函数。如果没有使用,编译器就不生成了默认构造函数了。
2 深拷贝和浅拷贝
浅拷贝:只是对指针的靠背,拷贝后两个指针指向同一个内存空间。
深拷贝:不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针。
重点:系统默认生成的拷贝构造函数是浅拷贝。
2.1 浅拷贝存在的问题
例如程序:
#include <iostream>
#include "string.h"
using namespace std;
class Test{
public:
char *buf;
Test(void){
buf=NULL;
}
Test(const char* str){
buf=new char[strlen(str)+1];
strcpy(buf,str);
}
~Test(){
if(buf!=NULL){
delete buf;
buf=NULL;
}
}
};
int main() {
Test t1("hello");
Test t2=t1;
cout<<"(t1.buf==t2.buf)?"<<(t1.buf==t2.buf?"Yes":"No")<<endl;
return 0;
}
Test类的buf是一个字符指针,在带参数的构造函数中为它分配了一块堆内存来存放字符串,然后在析构函数中又将堆内存释放。在main函数中 Test t2=t1;会调用默认的拷贝构造函数。因此输出结果如下,导致程序崩溃。
输出结构可以看出,默认的拷贝构造函数只是简单地把两个对象的指针做赋值运算,它们指向的仍然是同一个地址,当产生两次析构的时候,程序会释放同一块堆内存,然后崩溃。
总结浅拷贝会出现的问题:
(1)浅拷贝只是拷贝了指针,使两个对象的指针指向同一地址,在对象结束的时候,会造成同一块内存资源析构两次,造成程序崩溃。
(2)指针指向同一块内存,任何一方有改动都会影响另一方。
(3)在释放内存时,后者的内存先释放会导致前者内存也释放,导致空间不能再被利用。
2.1 浅拷贝问题的解决办法
可以在类中添加一个自定义的拷贝构造函数解决两次析构的问题。
如:
Test(Test &test){
buf=new char[strlen(test.buf)+1];
strcpy(buf,test.buf);
}
总代码:
#include <iostream>
#include "string.h"
using namespace std;
class Test{
public:
char *buf;
Test(void){
buf=NULL;
}
Test(const char* str){
buf=new char[strlen(str)+1];
strcpy(buf,str);
}
Test(Test &test){
buf=new char[strlen(test.buf)+1];
strcpy(buf,test.buf);
}
~Test(){
if(buf!=NULL){
delete buf;
buf=NULL;
}
}
};
int main() {
Test t1("hello");
Test t2=t1;
cout<<"(t1.buf==t2.buf)?"<<(t1.buf==t2.buf?"Yes":"No")<<endl;
return 0;
}
由于buf又分配了一块堆内存来保存字符串,t1和t2的buf指向了不同的堆内存,析构时就不会发生程序崩溃了,也就是深拷贝。
输出结果:
3 拷贝构造函数与赋值函数
区别:
1、拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区。
赋值函数是对于一个已经被初始化的对象来进行operator=操作。
例如:
Class A;
A a;
A b=a; //调用拷贝构造函数
A b(a); //调用拷贝构造函数
Class A;
A a;
A b;
b=a; //调用赋值函数
2、当数据成员包含指针对象的时候,普遍有两种处理需求:第一种是拷贝指针对象;另一种是引用指针对象。
拷贝构造函数在大多数情况下是拷贝;赋值函数则是引用对象。
3、实现方式不同。
拷贝构造函数首先是一个构造函数,它调用的时候是通过参数传进来的那个对象来初始化产生一个对象。
赋值函数则是把一个对象赋值给一个原有的对象。
因此,如果原来的对象中有内存分配,要先把内存释放掉,而且还需要检查一下两个对象是不是同一个对象,如果是,就无需任何操作。