在C++中,在用一个对象初始化另一个对象时,只复制了成员,并没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制,也就是我们说的浅拷贝。
浅拷贝
通俗的理解,浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
请看下面的代码:
//浅拷贝
class String{
public:
String(const char* str = "")
:_str(new char[strlen(str) + 1]){
strcpy(_str, str);
}
//拷贝构造函数
String(String& s) {
_str = s._str;
}
String& operator=(String& s) {
_str = s._str;
}
private:
char* _str;
};
上面的代码,其拷贝构造函数和赋值运算符重载时,都是用的浅拷贝,浅拷贝只拷贝指针
String s1("hello world");
String s2 = s1;
String s3;
s3 = s1;
如上代码,对象s1的成员变量_str开辟了一段内存空间,然后对象s2和s3的成员变量_str也指向这段内存空间,如图:
这样的后果就是,如果改变了s1、s2和s3中的任意一个对象的成员变量_str,就会导致其他两个的_str也被改变
其实如果不自己实现拷贝构造函数,编译器自动实现的话,就是实现的浅拷贝!
浅拷贝还有一个问题,例如在这个String类中,加上析构函数的代码:
~String() {
delelte[] _str;
_str = NULL;
}
如果对象s1和s2是浅拷贝的话,即使这两个对象没有被改动影响,但是出了作用域,调用析构函数的时候,会对一块内存空间释放两次。
深拷贝
上面我们发现,浅拷贝有许多问题,因此我们需要用到深拷贝,把拷贝构造函数和赋值运算符重载重写,代码如下:
String(const String& s)
:_str(new char[strlen(s._str) + 1]){
strcpy(_str, s._str);
}
String& operator=(const String& s) {
if(&s != this) {
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
由代码可以看出,拷贝构造函数和赋值运算符重载的时候,会新开辟一段内存空间,然后再把传入的str的值拷贝一份,如图:
注意这段代码:
String& operator=(const String& s) {
if(&s != this) {
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
这个函数是赋值运算符重载,这种写法是传统写法,我在这里讲几个容易踩的坑
1.没有释放原先_str指向的内存空间,容易造成你内存泄露(现代写法不用手动去释放原有的内存空间)
2.判断是否是给自身赋值,如s1 = s1(这点需要注意)
总结
这篇博客只是简单的解释一下C++实现类的时候浅拷贝和深拷贝的区别,这两点也是很重要的知识点,如果编程过程中不注意就可能会出现各种bug。
使用浅拷贝可能会导致以下后果:
1.如果使用其中一个指针更改指向内存空间的内容,则其他的指针指向内存空间的值也会被改变
2.可能会造成对申请内存空间的重复释放
所以我们应该避免使用浅拷贝,在类中手动实现拷贝构造函数和赋值运算符重载