首先,先看一下下面的代码会出现什么问题?
class String
{
public:
String(const char* data = "")
{
if (NULL == data)
{
_data = new char[1];
_data = "\0";
}
else
{
_data = new char[strlen(data) + 1];
strcpy(_data, data);
}
}
~String()
{
if (_data)
{
delete[] _data;
_data = NULL;
}
}
String(const String& s)
:_data(s._data)
{}
private:
char* _data;
};
void test()
{
String str1("hello world");
String str2(str1);
}
上面的程序会奔溃,让我们来分析原因:
这就是所谓的浅拷贝,也称位拷贝,编译器只是直接将指针的值拷贝过来,结果多个对象共用同一块内存,当一个对象将这块内存释放掉之后,另一些对象不知道该块空间已经还给了系统,以为还有效,所以在对这段内存进行操作的时候,发生了访问违规。
为了解决这个问题,我们引入深拷贝。
String::String(const String& s)//深拷贝
:_data(new char[strlen(s._data) + 1])
{
strcpy(_data, s._data);
}
上面代码则是str2,重新开辟了一块空间,并将str1里的值拷贝到str2这块空间中。
深拷贝中赋值运算符的重载:
下面有两种方法,我们来看看哪种方法比较。
方法一:
String& operator=(const String&s)
{
if (this != &s)
{
delete[] _data;
_data = new char[strlen(s._data) + 1];
strcpy(_data, s._data);
}
return *this;
}
方法二:
String& operator=(const String&s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._data) + 1];
strcpy(tmp, s._data);
delete[] _data;
_data = tmp;
}
return *this;
}
一般情况下,两种方法都可以,但第二种方法更优一些。
方法一中,先将旧的空间释放掉,然后又重新开辟出与形参同样大小新的空间,然后将形参的内容拷贝到此空间,此方法有一个弊端,就是当空间申请失败时,不仅没有成功赋值,也破坏了原有被赋值的对象。
方法二中,先开辟出新空间,将这个新空间赋值给tmp这个临时变量,就算开辟失败也不会破坏原有的对象。
最后还有一点,就是这里的返回值是为了支持链式访问。
下面就是面试中String类深拷贝的写法,一般没有特殊要求,将必要的成员的成员函数给出就可以了。
class String
{
public:
String(const char* data = "")
{
if (NULL == data)
{
_data = new char[1];
_data = "\0";
}
else
{
_data = new char[strlen(data) + 1];
strcpy(_data, data);
}
}
~String()
{
if (_data)
{
delete[] _data;
_data = NULL;
}
}
String(const String& s)
:_data(new char[strlen(s._data)+1])
{
strcpy(_data, s._data);
}
String& operator=(const String&s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._data) + 1];
strcpy(tmp, s._data);
delete[] _data;
_data = tmp;
}
return *this;
}
private:
char* _data;
};
上面所写的拷贝构造函数和赋值运算符重载函数属于传统写法,下面我们一起来看看它们的现代写法:
拷贝构造函数的现代写法:
String(const String& s)
:_data(NULL)
{
String strtmp(s._data);
std::swap(strtmp._data,_data);
}
赋值运算符重载函数的两种现代写法:
String& operator=(const String&s)
{
std::swap(_data,s._data);
return *this;//为了支持链式访问
}
String& operator=(const String&s)
{
if (this->_data != s._data)
{
String tmp(s);
std::swap(tmp._data, _data);
}
return *this;//为了支持链式访问
}
两种写法不同的是,第一个的拷贝构造函数是在参数列表中完成的,第二种是在函数体内完成的。
以上是关于String类的深浅拷贝问题,当然在面试中也可以写现代写法。