拷贝构造函数也是类的一个重载版本的构造函数,它可以用一个已知的对象初始化一个被建立的同类新对象。该函数的形式参数是本类对象的常引用,所以与普通构造函数在形式参数上有很是明显的区别。跟构造函数同样,C++为每个类定义了一个默认的拷贝构造函数,能够实现将实参对象的数据成员值复制到新建立的当前对象对应的数据成员中。用户能够根据须要定义本身的拷贝构造函数,从而实现同类对象之间数据成员的值传递。ios
拷贝构造函数的定义格式以下:函数
class 类名
{
public:spa
类名(const 类名&对象名);
...
};3d
拷贝构造函数是一种特殊的构造函数,在建立类的对象时若是实际参数是本类的对象,则调用拷贝构造函数。指针
如下3种状况系统自动调用拷贝构造函数:code
给一个程序帮助理解:对象
1 #include"stdafx.h" 2 #include<iostream> 3 using namespace std; 4 5 class TDate 6 { 7 private: 8 int year,month,day; 9 public: 10 TDate(int y=2013,int m=9,int d=28); 11 TDate(const TDate &date); 12 ~TDate() 13 { 14 cout<<"Deconstructor called."<<endl; 15 } 16 void Print(); 17 }; 18 19 TDate::TDate(int y,int m,int d) 20 { 21 year=y; 22 month=m; 23 day=d; 24 cout<<"Constructor called."<<endl; 25 } 26 27 TDate::TDate(const TDate &date) 28 { 29 year=date.year; 30 month=date.month; 31 day=date.day; 32 cout<<"Copy Constructor called."<<endl; 33 } 34 35 void TDate::Print() 36 { 37 cout<<year<<"."<<month<<"."<<day<<endl; 38 } 39 40 TDate f(TDate Q) 41 { 42 TDate R(Q); 43 return Q; 44 } 45 46 void main() 47 { 48 TDate day1(2013,1,1);//调用普通带参构造函数 49 TDate day3;//调用普通构造函数,使用默认参数值 50 TDate day2(day1);//调用拷贝构造函数 51 TDate day4=day2;//调用拷贝构造函数 52 day3=day2;//复制语句,不调用任何构造函数 53 day3=f(day2);//实参传值给形参Q调用拷贝构造函数 54 //f内部定义对象R(Q)时调用拷贝构造函数 55 //返回Q调用拷贝构造函数 56 day3.Print();
前3个析构函数是f函数调用结束时引发的,后4个析构函数是day1-day4生命期结束调用的,他们的调用顺序与构造函数彻底相反。blog
系统为每个类提供的默认拷贝构造函数,能够实现将源对象全部数据成员的值逐一赋值给目标对象相应的数据成员。若是讲上面程序中拷贝构造函数的原型声明及定义去掉,并不影响程序的正确执行,结果以下:内存
能够看到,析构函数调用了7次,说明有5次调用的是析构函数。那么何时必须为类定义拷贝构造函数呢?字符串
一般,若是一个类包含指向动态存储空间指针类型的数据成员,而且经过该指针在构造函数中动态申请了空间,则必须为该类定义一个拷贝构造函数,不然在析构时容易出现意外错误。
1 #include"stdafx.h" 2 #include<iostream> 3 using namespace std; 4 5 class String 6 { 7 private: 8 char *S; 9 public: 10 String(char *p=0); 11 // String(const String &s1); 12 ~String(); 13 void Show(); 14 }; 15 16 String::String(char *p) 17 { 18 if(p) 19 { 20 S=new char[strlen(p)+1]; 21 strcpy(S,p); 22 } 23 else S=0; 24 } 25 /* 26 String::String(const String &s1) 27 { 28 if(s1.S) 29 { 30 S=new char[strlen(s1.S)+1]; 31 strcpy(S,s1.S); 32 } 33 else S=0; 34 } 35 */ 36 String::~String() 37 { 38 if(S) delete[]S; 39 } 40 41 void String::Show() 42 { 43 cout<<"S="<<S<<endl; 44 } 45 46 void main() 47 { 48 String s1("teacher"); 49 String s2(s1); 50 s1.Show(); 51 s2.Show(); 52 }
该程序在编译无error也无warning,但在执行后会报错,中断执行。由于在执行String s1("teacher");语句时,构造函数动态地分配存储空间,并将返回的地址赋给对象s1的成员S,而后把teacher的内容拷贝到这块空间。
因为String没有定义拷贝构造函数,所以当语句Sttring s2(s1);定义对象s2时,系统将调用默认的拷贝构造函数,负责将对象s1的数据成员S中存放的地址值赋值给对象s2的数据成员S。
上图中,对象s1复制给对象s2的仅是其数据成员S的值,并无把S所指向的动态存储空间进行复制,这种复制称为浅拷贝。
浅拷贝的反作用是在调用s1.Show();与s2.show();时看不出有什么问题,由于两个对象的成员S所指向的存储区域是相同的,都能正确访问。可是,当遇到对象的生命期结束须要撤销对象时,首先由s2对象调用析构函数,将S成员所指向的字符串teacher所在的动态空间释放,其数据成员S成为悬挂指针。那么在s1自动调用析构函数的时候,没法正确执行析构函数代码delete[]S,从而致使出错。
咱们经过定义拷贝函数实现深拷贝能够解决浅拷贝所带来的指针悬挂问题。深拷贝指不复制指针值自己,而是复制指针所指向的动态空间中的内容。这样,两个对象的指针成员就拥有不一样的地址值,指向不一样的动态存储空间首地址,而两个动态空间的内容彻底同样。
在上面的程序中添加被注释的语句,在执行String s2(s1);时,使用对象s1去建立对象s2,调用自定义的拷贝构造函数,当前新对象经过数据成员S另外申请了一快内存,而后将已知对象s1的数据成员S复制到当前对象s2的S所指向的内存空间。
此时运行程序在析构时不存在指针悬挂的问题,程序能够正确运行。