深入解析浅拷贝和深拷贝

浅拷贝

浅拷贝,也称位拷贝,编译器只是将对象中的值拷贝过来,如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进行操作时就会出现访问违规

先看一段代码

class String
{
public :
String(const char *ptr="")//构造函数。默认放\0
:_ptr(new char[strlen(ptr)+1])//strlen计算长度不加\0
{
strcpy(_ptr,ptr);
}

~String()
{
if(_ptr)
{
cout<<this<<endl;
delete[] _ptr;
}
}

private:
char *_ptr;
};

int main()
{
String s1("hello");
String s2(s1);

return 0;
}
执行结果如下:

可以发现这里程序会崩溃,什么原因导致崩溃

这就是浅拷贝,多个对象共享同一份资源,造成的问题也显而易见,一份资源被释放了多次。

深拷贝

深拷贝就是给所构造的对象重新申请了一段空间

class String
{
public:
String(const char *str="")
{
if(str==NULL)
str="";
_pStr=new char[strlen(str)+1];
strcpy(_pStr,str);
}
String(const String& s)//深拷贝
:_pStr(new char[strlen(s._pStr)+1])//重新申请了一段空间
{
strcpy(_pStr,s._pStr);
}

//赋值运算符重载
//方法一
//String& operator=(const String s)
//{
// if(this != &s)
// {
// delete[] _pStr;
// _pStr=new char[strlen(s._pStr )+1];
// strcpy(_pStr,s._pStr);
// }
// return *this;//为了支持链式访问
//}

//方法二(优)
String& operator=(const String& s)
{
if(this!=&s)//自己不能拷贝自己
{
char *tmp=new char[strlen(s._pStr )+1];
strcpy(tmp,s._pStr );
_pStr=tmp;

}
return *this;
}

~String()
{
if(_pStr)
{
delete[] _pStr;
}
}

private:
char *_pStr;
};

int main()
{
String s1("hello");
String s2(s1);
return 0;
}


 

一般情况下,上面对赋值运算符重载的两种写法都可以,但是相对而言,第二种更优一点,对于第一种,先释放了旧空间,但是如果下面用new开辟新空间时有可能失败,抛出异常而这时将s2赋值给s3,不仅没有赋值成功,而且也破坏了原有的s3对象,对于第二种,先开辟新空间,将新空间赋给一个临时变量,就算这时空间开辟失败,也不会影响原本s3对象

引用计数

当多个对象共享一块资源时,要保证该资源只释放一次,只需记录有多少个对象在使用该资源即可,没减少(增加)一个对象使用,给该计数减一(加一),当最后一个对象不使用时,该对象负责将资源释放掉即可

class String
{
public:
String()
{}

String(const char *str)
{
if(str==NULL)
{
_str=new char[4+1];//多申请一个int来存储计数器
_str+=4;
_str='\0';
}
else
{
_str=new char[strlen(str)+1+4];
_str+=4;
strcpy(_str,str);
}
GetCount(_str)=1;//将计数器初始值设为1
}
String(const String& s)
:_str(s._str)
{
GetCount(_str)++;
}

//s1=s2;
String operator=(const String& s)
{
if(_str!=s._str )
{
//1.当s1的引用计数为1时
//a.释放空间
//b.改变s1指针的指向
//c.s2的引用计数加1
Release();
//当s1的引用计数大于1时
//a.s1引用计数减1
//b.同上b,c
_str=s._str ;
++GetCount(_str);//引用计数加1
}
return *this;
}

~String()
{
Release();
}
private:
int& GetCount(char * str)
{
return *(int *)(str-4);//因为这块内存类型为char
}

void Release()
{
if(_str!=NULL && (--GetCount(_str))==0)
delete[] (_str-4);//一定要释放存储计数器的空间
}
private:
char *_str;
};

int main()
{
String s1("hello");
String s2(s1);

String s3("world");
String s4(s3);
String s5(s3);
return 0;
}
上面的代码是在构造的时候多申请4字节空间来存储计数器,从而实现计数

但是这样还是有问题,看下面的代码

class String
{
public:
String()
{}

String(const char *str)
{
if(str==NULL)
{
_str=new char[4+1];//多申请一个int来存储计数器
_str+=4;
_str='\0';
}
else
{
_str=new char[strlen(str)+1+4];
_str+=4;
strcpy(_str,str);
}
GetCount(_str)=1;//将计数器初始值设为1
}
String(const String& s)
:_str(s._str)
{
GetCount(_str)++;
}

//s1=s2;
String operator=(const String& s)
{
if(_str!=s._str )
{
//1.当s1的引用计数为1时
//a.释放空间
//b.改变s1指针的指向
//c.s2的引用计数加1
Release();
//当s1的引用计数大于1时
//a.s1引用计数减1
//b.同上b,c
_str=s._str ;
++GetCount(_str);//引用计数加1
}
return *this;
}

char& operator[](size_t index)//可以用下标的方式来访问String类
{
return _str[index];
}
~String()
{
Release();
}
private:
int& GetCount(char * str)
{
return *(int *)(str-4);//因为这块内存类型为char
}

void Release()
{
if(_str!=NULL && (--GetCount(_str))==0)
delete[] (_str-4);//一定要释放存储计数器的空间
}
private:
char *_str;
};

int main()
{
String s1("hello");
String s2(s1);

String s3(s1);
s3[1]='a';
return 0;
}


当共用同一块空间的对象的任一对象修改字符串中的值,则会导致所有共用这块空间中的内容全被改变,我们只想改变s3的值,但是和它共用的两个对象的值也全改变,这就引出了写时拷贝

写时拷贝

有多个对象共享同一空间时,当对其中一个对象只读时,不会有什么影响,但是想要改变某个对象的值时,这时就要为这个对象重新分配空间,代码实现如下

class String
{
public:
String()
{}

String(const char *str)
{
if(str==NULL)
{
_str=new char[4+1];//多申请一个int来存储计数器
_str+=4;
_str='\0';
}
else
{
_str=new char[strlen(str)+1+4];
_str+=4;
strcpy(_str,str);
}
GetCount()=1;//将计数器初始值设为1
}
String(const String& s)
:_str(s._str)
{
GetCount()++;
}

//s1=s2;
String operator=(const String& s)
{
if(_str!=s._str )
{

Release();
_str=s._str ;
++GetCount();//引用计数加1
}
return *this;
}

char& operator[](size_t index)//可以用下标的方式来访问String类
{
if(GetCount()>1)
{
--GetCount();
char* pTemp=new char[strlen(_str)+1+4];
pTemp+=4;
strcpy(pTemp,_str);
_str=pTemp;
GetCount()=1;//将新空间置1
}
return _str[index];
}


~String()
{
Release();
}
private:
int& GetCount()
{
return *(int *)(_str-4);//因为这块内存类型为char
}

void Release()
{
if(_str!=NULL && (--GetCount())==0)
delete[] (_str-4);//一定要释放存储计数器的空间
}
private:
char *_str;
};

int main()
{
String s1("hello");
String s2(s1);

String s3(s1);
s3[1]='a';
return 0;
}

猜你喜欢

转载自www.cnblogs.com/zlshmily/p/9998947.html