回 帖 发 新 帖 刷新版面

主题:[原创]拷贝构造函数的作用

拷贝构造函数的作用
                                                -----小峰
    如果你没有为你的程序添加一个拷贝构造函数,那么编译器也会向你的程序添加一个默认的拷贝构造函数.在一般情况下使用编译器的默认拷贝构造函数就可以了,但是如果你的类的成员数据有指针变量的存在,那么你最好自己构造一个拷贝构造函数,否则你的程序可能隐藏着许多错误.
请看下面这一个程序,存在多少错误?

#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)中发生的。第三、四次是在主函数发生的。

回复列表 (共6个回复)

沙发

To 小峰:

My suggestion is you don't write your own String class.

STL and VC both have defined more mature string classes: string or CString respectively!!!!

Default copy constructor is a shallow clone, if memory allocation is needed, shallow clone is not enough. In that case, you'd better write your own copy constructor!!!

小峰  is correct on this point!!!

Thanks!

板凳

justforfun626你好.

本程序写的一个简单的String类只是想在此类中说明深拷贝的作用,以及什么时候复制构造函数会被调用.并没有想要编写一个与标准模板库与VC++的字符串处理类进行比美的类.请不要误解啊!

3 楼

嗯,谢谢楼主小峰,学习中!

4 楼

写的不错,这种基础问题,需要大家重视

5 楼


头看晕了也要顶

6 楼

谢谢各位朋友的支持哈,谢过了!

我来回复

您尚未登录,请登录后再回复。点此登录或注册