C++中的深拷贝与浅拷贝
1. 概念
在C++涉及指针的部分中,有 深拷贝和 浅拷贝 两个概念,让我们通过例子来理解一下。C++中,字符串是通过首地址来表示的,如图所示:
图中,字符串用 s1 表示,而 s1 是字符串 “HELLO” 的首地址(字符串以 ‘\0’ 作为结束标志),通过寻址,即可实现字符串操作。下图中创建了一个字符串 s2 ,上面的表示 深拷贝 ,下面的表示 浅拷贝 。
由上图可以看出,深拷贝又创建了一份新的字符串副本, s2 就是字符串副本的首地址,而浅拷贝仅仅是给 s1 起了一个别名 s2 。那么,这两种拷贝方式有什么需要注意的呢?
1.因为 s1 和 s2 指向同一块内存区域,所以,如果 s1 指向的内容发生改变,那么 s2 指向的内容随之改变。
2.如果 s2 原来指向一块内存,在没有释放 s2 指向的内存的情况下就把 s1 浅拷贝给 s2 ,那么 s2 原来指向的空间就发生了内存泄漏,造成程序无法将这块内存还给计算机。(因为此时没有指针指向这块区域)
2. 实际应用
在C++中,有一个 String 类,内部就涉及到了深拷贝和浅拷贝。程序(下面程序块和 String 源码类似)如下:
class String{
public:
String(const char* cstr = 0); //构造函数
String(const String& str); //拷贝构造函数
String& operator= (const String& str); //拷贝赋值函数
~String(); //析构函数
char* get_c_char() const { return m_data; }
private:
char* m_data;
};
上面的函数分别是 构造函数 、拷贝构造函数 、拷贝赋值函数 、析构函数 。
构造函数 和 拷贝构造函数 的不同之处在于其参数的区别,拷贝构造函数 的参数是自身类型的对象。下面就每个函数分别介绍。
2.1 构造函数
inline String::String(const char* cstr = 0){
if (cstr){
m_data = new char[strlen(cstr) + 1]; //+1 为了存储'\0'
strcpy(m_data, cstr);
}
else{
m_data = new char[1];
*m_data = '\0';
}
}
2.2 拷贝构造函数
inline String::String(const String& str){
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
2.3 拷贝赋值函数
inline String& String::operator=(const String& str){
if (this == &str){ //检测是否是自身赋值
return *this;
}
delete[] m_data;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return *this;
}
这里用的就是 深拷贝 ,如果直接使用 浅拷贝 ,会导致 str 原来指向的内存没有指针所指,即发生了内存泄漏。(C++默认的是 浅拷贝 )此外,与上面的两个构造函数不同,拷贝复制函数的对象本身已经存在,所以为了避免对自身赋值造成错误,加入了判断条件。
注:不加判断的话,对自身赋值会错误,因为 delete 了原来字符数组的内容,后面的拷贝操作 strcpy 会失败。
2.4 析构函数
inline String::~String(){
delete[] m_data;
}
由于构造函数中都采用了 new 运算符动态创建字符数组的方式,所以在析构函数中要 delete 掉内存。