深拷贝与浅拷贝
简单的来说,
【浅拷贝】
是增加了一个指针,指向原来已经存在的内存。而
【深拷贝】
是增加了一个指针,并新开辟了一块空间
让指针指向这块新开辟的空间。
【浅拷贝】
在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误
浅拷贝
为了形象化说明什么是深拷贝和浅拷贝,我们就先写一个String类
类里面包含
【构造函数】
,
【拷贝构造函数】
,
【赋值运算符重载】
,以及
【析构函数】
和
【输出操作符“<<”的重载】
-
class
String
-
{
-
public
:
-
String(
const
char
*pStr =
“”
)
-
{
-
if
(NULL == pStr)
-
{
-
pstr =
new
char
[1];
-
*pstr =
‘\0’
;
-
}
-
else
-
{
-
pstr =
new
char
[strlen(pStr)+1];
//加1,某位是’\0′
-
strcpy(pstr,pStr);
//用拷贝字符串的函数
-
}
-
}
-
-
String(
const
String &s)
-
:pstr(s.pstr)
//浅拷贝的问题,指向同一块空间,可能造成释放的错误 ,这是浅拷贝的缺点
-
{}
-
-
String& operator=(
const
String&s)
-
{
-
if
(
this
!= &s)
-
{
-
delete
[] pstr;
//将原来所指向的空间释放
-
pstr = s.pstr;
//让pstr重新指向s的pstr所指向的空间(也会导致错误)
-
}
-
return
*
this
;
-
}
-
-
~String()
-
{
-
if
(NULL != pstr)
-
{
-
delete
[] pstr;
//释放指针所指向的内容
-
pstr = NULL;
//将指针置为空
-
}
-
}
-
friend
ostream&operator<<(ostream & _cout,
const
String &s)
-
{
-
_cout<<s.pstr;
-
return
_cout;
-
}
-
private
:
-
char
*pstr;
-
};
经过测试之后,在某种情况下是可以正常运行的,在特定情况下是不可以正常的运行的
举一个不能正常运行的例子
-
int
main()
-
{
-
String s1(
“sss”
);
-
String s2(s1);
-
String s3(NULL);
-
s3 = s1;
-
cout<<s1<<endl;
-
cout<<s2<<endl;
-
cout<<s3<<endl;
-
return
0;
-
}
在该例子中,我们有三个String类的对象,s1调用
【构造函数】
存入字符”sss”
s2调用
【拷贝构造函数】
来利用s1进行初始化,s3则用
【赋值运算符】
来进行初始化
黑框框里输出了三个“sss”
然而!
这是为什么呢?
通过监视,我们发现他们指向的都是同一块空间,因为地址都是
【0x01044570】
在让我们看看
【析构函数】
-
~String()
-
{
-
if
(pstr != NULL)
-
{
-
delete
[] pstr;
-
pstr = NULL;
-
}
-
}
当我们释放s3的时候,可以正常释放
然而当释放s2的时候,由于
【s3已经释放过了】
,所以s2所指向的这段空间已经不属于s1或者s2了
此时我们调用delete释放的时候,必然会崩溃
(毕竟人家本来就不属于你呀)
深拷贝
深拷贝以及深浅拷贝的对比
深拷贝和浅拷贝的不同之处,仅仅在于修改了下
【拷贝构造函数】
,以及
【赋值运算符的重载】
-
String(
const
String &s)
-
:pstr(
new
char
[strlen(s.pstr)+1])
-
{
-
strcpy(pstr,s.pstr);
-
}
-
-
String& operator=(
const
String &s)
-
{
-
if
(
this
!= &s)
-
{
-
char
* tmp =
new
char
[strlen(s.pstr)+1];
//动态开辟一个临时变量,然后将pstr指向这一个新的临时变量里
-
delete
[] pstr;
//将原来的空间进行释放
-
strcpy(tmp,s.pstr);
//将s.pstr里的内容复制到临时变量中
-
pstr = tmp;
//pstr指向临时变量的这段空间
-
}
-
return
*
this
;
-
}
对比一下浅拷贝的
【拷贝构造函数】
和
【赋值运算符重载】
-
String(
const
String &s)
-
:pstr(s.pstr)
//浅拷贝的问题,指向同一块空间,可能造成释放的错误 ,这是浅拷贝的缺点
-
{}
-
-
String& operator=(
const
String&s)
-
{
-
if
(
this
!= &s)
-
{
-
delete
[] pstr;
//将原来所指向的空间释放
-
pstr = s.pstr;
//让pstr重新指向s的pstr所指向的空间(也会导致错误)
-
}
-
return
*
this
;
-
}
深拷贝完整版
-
class
String
-
{
-
public
:
-
String(
const
char
* pStr =
“”
)
-
{
-
cout<<
“String()”
<<endl;
-
if
(NULL == pStr)
-
{
-
pstr =
new
char
[1];
-
*pstr =
‘\0’
;
-
}
-
else
-
{
-
pstr =
new
char
[strlen(pStr)+1];
-
strcpy(pstr,pStr);
-
}
-
}
-
String(
const
String &s)
-
:pstr(
new
char
[strlen(s.pstr)+1])
-
{
-
strcpy(pstr,s.pstr);
-
}
-
-
String& operator=(
const
String &s)
-
{
-
if
(
this
!= &s)
-
{
-
char
* tmp =
new
char
[strlen(s.pstr)+1];
//pstr;
-
delete
[] pstr;
-
strcpy(tmp,s.pstr);
-
pstr = tmp;
-
}
-
return
*
this
;
-
}
-
-
~String()
-
{
-
if
(NULL != pstr)
-
{
-
delete
[] pstr;
-
pstr = NULL;
-
}
-
}
-
private
:
-
char
*pstr;
-
};
除此之外,我们可以简化一下深拷贝的
【拷贝构造函数】
,
【赋值运算符重载】
-
<span style=
“color:#000000;”
>String(
const
String& s)
-
:_ptr(NULL)
-
{
-
String temp(s._ptr);
-
std::swap(_ptr, temp._ptr);
-
}
-
</span>
在
【拷贝构造函数】
里用s定义临时变量,临时变量会自动调用构造函数开辟空间
然后用swap这个函数交换两个变量之间的内容
原来的内容在temp,并且出了
【拷贝构造函数】
就销毁了,避免了内存泄漏
-
String& operator=(
const
String& s)
-
{
-
if
(
this
!= &s)
-
{
-
String temp(s);
-
swap(_ptr, temp._ptr);
-
}
-
return
*
this
;
-
}
-
String& operator=(
const
String& s)
-
{
-
if
(
this
!= &s)
-
{
-
String temp(s._ptr);
-
swap(_ptr,temp._ptr);
-
}
-
return
*
this
;
-
}
-
String& operator=(String temp)
-
{
-
swap(_ptr,temp._ptr);
-
return
*
this
;
-
}
在
【赋值运算符重载】
里,也可以用到类似的方法。在第三个方法里,直接传入一个临时变量,连if判断都可以省去了
总结
【浅拷贝】
只是增加了一个指针,指向已存在对象的内存。
【深拷贝】
是增加了一个指针,并
新开辟了一块空间
,让指针指向这块新开辟的空间。
【浅拷贝】
在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误