http://www.programfan.com/club/showtxt.asp?id=175861
拷贝构造函数的作用 -----小峰 如果你没有为你的程序添加一个拷贝构造函数,那么编译器也会向你的程序添加一个默认的拷贝构造函数.在一般情况下使用编译器的默认拷贝构造函数就可以了,但是如果你的类的成员数据有指针变量的存在,那么你最好自己构造一个拷贝构造函数,否则你的程序可能隐藏着许多错误. 请看下面这一个程序,存在多少错误?
#include "stdafx.h" #include "iostream.h" #include "string.h" class String { int nLen; char *cArray; public: String(){nLen=0; cArray=NULL;} ~String(){delete []cArray; nLen=0;} String(const char *str) { int len=strlen(str)+1; cArray=new char[len]; strcpy(cArray,str); nLen=len; } void operator=(const char *str) { if(nLen) delete []cArray; int len=strlen(str)+1; cArray=new char[len]; strcpy(cArray,str); nLen=len; } char *GetBuffer()const{return cArray;} //---------------------------------------------------- String operator=(String p) { if(nLen) delete []cArray; strcpy(p.cArray,p.GetBuffer()); int len=strlen(p.GetBuffer())+1; cArray=new char[len]; nLen=len; strcpy(cArray,p.GetBuffer()); return *this; } //--------------------------------------------------- };
int main(int argc, char* argv[]) { String str("meihaofeng"); char *p=str.GetBuffer(); cout<<p<<endl; str="mei"; p=str.GetBuffer(); cout<<p<<endl; String s="hello!"; str=s;//这里开始出错 p=str.GetBuffer(); cout<<p<<endl; return 0; } 初看也许你会觉得没有什么错误啊,但你将这段完整的程序拿去运行一下(编译链接是能通过的),啊!那些错误有些惊人.下面让我们来分析一下,出现那些错误的原因. 加粗的那个函数是出错的根本,主要就是针对这个函数进行分析.在主函数是从“str=s;”这一句开始出错的。执行str=s对应String operator=(String p)函数上。首先将“s”作为参数拷贝给String operator=(String p)的形参p。在这个过程中,s的两个成员数据 nLen和cArray也相应的拷贝给p的nLen和cArray.但是cArray是一个指针,拷贝之后p.cArray的值和s.cArray的值是相等的,可以知道这两个指针变量都指向同一片内存区域,这一个内存区域就是存储“hello!”的内存区域。当String operator=(String p)这个函数执行完后,由于对象p超出了它的作用范围,所以调用对象p的析构函数,在析构函数中释放cArray所指向的内存,此时释放cArray所指向的的内存相当于释放p.cArray或主函数中s.cArray所指向的内在区域。释放完后返回*this对象,注意,返回的*this这个对象将拷贝给主函数的一个临时的对象,之后就释放这个临时对象的内存。在将*this拷贝给一个临时对象的过程如同将s作为实参传给形参p一样,使临时对象的两个数据成员nLen和cArray与(*this)对象的nLen和cArray相等,同理(*this).cArray与临时对象的cArray指向现一片内存区域。在释放这个临时对象时先调用这它的析构函数,从上面的程序可以知道这个*this代表主函数的一个对象str,所以在析构函数中就释放str.cArray所指向的内存。 从String operator=(String p)中由前面的分析可知,主函数中s.cArray以及str.cArray所指向的内存已经被释放了。返回到主函数通过p=str.GetBuffer();cout<<p<<endl;打印str.cArray的值也就打印出了错误的值。当主函数中str与s两个对象的生存周期结束后,这两个对象的析构函数会被再次调用。在调用析构函数时,又会释放s.cArray和str.cArray所指向的内存,由于在String operator=(String p)函数中这两个指针所指向的内存区域已经被释放了,所以在主函数中再次释放就会出错。这些就是程序中隐藏着的错误。
最好的解决方法就是不要编译器添加的拷贝构造函数,而是自己手动添加一个,并在这个拷贝构造函数中执行一此操作。下面的程序修补了上面程序的错误,程序如下: #include "stdafx.h" #include "iostream.h" #include "string.h"
class String { int nLen; char *cArray; public: String(){nLen=0; cArray=NULL;} ~String() { cout<<"~String()"<<endl; delete []cArray; nLen=0; } String(const char *str) { int len=strlen(str)+1; cArray=new char[len]; strcpy(cArray,str); nLen=len; } void operator=(const char *str) { if(nLen) delete []cArray; int len=strlen(str)+1; cArray=new char[len]; strcpy(cArray,str); nLen=len; } char *GetBuffer()const{return cArray;} //---------------------------------------------------- String(const String & s)//自己添加的拷贝构造函数。 { cout<<"拷贝构造函数"<<endl; char *temp; temp=new char[s.nLen]; strcpy(temp,s.GetBuffer()); cArray=new char[s.nLen]; strcpy(cArray,temp); delete temp; } //---------------------------------------------------- String operator=(String p) { if(nLen) delete []cArray; strcpy(p.cArray,p.GetBuffer()); int len=strlen(p.GetBuffer())+1; cArray=new char[len]; nLen=len; strcpy(cArray,p.GetBuffer()); return *this; } //--------------------------------------------------- };
int main(int argc, char* argv[]) { String str("meihaofeng"); char *p=str.GetBuffer(); cout<<p<<endl; str="mei"; p=str.GetBuffer(); cout<<p<<endl; String s="hello!"; str=s; p=str.GetBuffer(); cout<<p<<endl; return 0; } 在拷贝构造函数中要为目标对象中的指针分配空间,并让指针指向分配的空间。并将源对象的指针所指向的空间里的值复制到分配的空间中。注意这里的目标对象,对应到上面的程序“str=s”中,str即为目标对象,s为源对象。在String operator=(String p)返回时临时对象为目标对象,*this为源对象。 下面是程序的运行结果: --------------------------------- meihaofeng mei 拷贝构造函数 拷贝构造函数 ~String() ~String() hello! ~String() ~String() --------------------------------- 从运行结果为可以看到拷贝构造函数被调用了两次,第一次是,执行str=s时,将实参s拷贝给String operator=(String p)函数的形参p. 第二次是,在String operator=(String p)返回里,将*this拷贝给一个临时对象。析构函数被调用了四次,从程序中也不难分析出是那四次. 第一、二是String operator=(String p)中发生的。第三、四次是在主函数发生的。
|