简单先理解一下深浅拷贝和写实拷贝的概念
深浅拷贝的概念大多数人是比较清楚的,简单来说就比如我申请了一个字符串str1=“abc”, 现在我想复制一个str2其内容和str1一模一样。
1.如果只是将str2指针指向str1的空间,这就是浅拷贝,因为str2使用的还是str1的空间,str1和str2只要有一个值改变了,另一个也会死之变化,这样虽然省去可开辟空间的消耗,但是数据是不安全的。
2.如果我给str2开辟了一个独立的空间,并且将str1中的值拷贝到这个空间里,这属于深拷贝。不仅拷贝了值还有自己的独立空间。显然数据变安全了,效率会损失。
3.写时拷贝的思想是解和深浅拷贝两者的优点,当数据需要被修改时用深拷贝(安全),不修改只读时用浅拷贝(效率高)。
深拷贝:
首先看一个字符串拷贝的例子(c语言风格)
class String {
public:
String(const char* str = "") {//默认值为空串,空串有\0,strlen计算时有一个空间
m_data = (char*)malloc(sizeof(char) * (strlen(str) + 1));
strcpy(m_data, str);
}
String(const String& s) { //深拷贝
m_data = (char*)malloc(sizeof(char) * (strlen(s.m_data) + 1));
//深拷贝开辟了自己的空间并拷贝了s.m_data的值;浅拷贝只改变m_data指针指向s.m_data
strcpy(m_data, s.m_data);
}
~String() {
free(m_data);
m_data = nullptr;
}
String& operator=(String& s) { //深赋值拷贝
if (this != & s) {
free(m_data);
m_data = (char*)malloc(sizeof(char) * (strlen(s.m_data) + 1));
strcpy(m_data, s.m_data);
}
return *this;
}
private:
char* m_data;
};
从内存的的角度看是这样的
在面对带指针的成员变量拷贝赋值时使用深拷贝能避免重复析构这一问题,还可以避免一个变量内容修改之后其它变量随之修改的问题,但是深拷贝会额外的开辟空间,效率不高。
可以考虑使用更好的方法
浅拷贝:
如果多个变量指向一个空间但是都不修改这个变量的话,可以考虑用浅拷贝,并加一个计数器,计数为0时才调动析构函数,即节省空间又避免重复析构。
思路如下:
核心代码如下:
class String {
public:
friend ostream& operator<<(ostream& out, const String& s);//重载输出流
String(const char* str = "") {//默认值为空串,有\0
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
String(const String& s) {
m_data = s.m_data; //浅拷贝
m_count++;
}
~String() {
/*delete[]m_data;
m_data = nullptr;*/
if (--m_count == 0) {
delete[]m_data;
m_data = nullptr;
}
}
String& operator=(const String& s) { //赋值
if (this != &s) {
m_data = s.m_data;
m_count++;
}
return *this;
}
private:
char* m_data;
static int m_count;//引用计数器
};
int String::m_count = 0;//静态成员类外初始化
ostream& operator<<(ostream &out, const String&s) {
out <<s.m_data;
return out;
}
上面这种思路用是浅拷贝,效率虽然更高,但缺陷就是只能访问数据,但是不能更改数据,否则就会影响所有对象的数据。
而且还有一个致命缺陷,引用计数器是所有对象共享的,如果String s1=“abc”;String s2=(s1);String s3=“xyz”; 那么s3的m_count就直接从s2的计数器基础上往上+1,但是s2和s3是不一样的数据,计数器应该从0增加。显然这种设计方法是错误的,使用范围太局限。还需要进一步改进。
写时拷贝:
如果拷贝后有时候还会修改这个变量,有时不修改,那么如何才能让它在修改变量时用到深拷贝,不修改时用浅拷贝?这样既能提高效率也能使数据访问更安全。---------这就要用到写时拷贝技术。
核心代码:
代码设计时增加了一个计数器类String_rep,里面含两个成员 char* m_data;和int m_count;一个保存string数据,一个用来计数,通过每个String对象都有一个单独的String_rep成员,这样每个String对象的计数器都是独立的,方便设计和管理。
#define DISPLAY //调试开关ifdef ... endif...
class String;
class String_rep;
class String_rep { //字符串引用计数器类
public:
friend class String;
friend ostream& operator<<(ostream& out, const String_rep& rep);//重载输出流
String_rep(const char* str=""):m_count(0)
{ //构造
#ifdef DISPLAY
cout << "Create String_rep Obj." << endl;
#endif // DISPLAY
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
~String_rep()
{ //析构
#ifdef DISPLAY
cout << "Delete String_rep Obj." << endl;
#endif // DISPLAY
delete[] m_data;
m_data = nullptr;
}
String_rep(const String_rep& rep) :m_count(0)
{//拷贝构造
m_data = rep.m_data; //浅拷贝
}
String_rep& operator=(const String_rep& rep)
{ //赋值语句。
if (this != &rep)
{
m_data = rep.m_data;
}
return *this;
}
public:
void increment()
{
m_count++;
}
void decrement()
{
m_count--;
if (m_count == 0)
delete this; //自杀式释放:先调动调用者String自身的析构函数,析构pn,
//然后调动String_rep的析构函数,回收m_data和m_count空间
}
char* Getdata()const
{
return m_data;
}
private:
char* m_data;
int m_count;
};
class String {
friend ostream& operator<<(ostream& out, const String& s);
public:
String(const char* str = ""):pn(new String_rep(str))
{
#ifdef DISPLAY
cout << "Create String Obj." << endl;
#endif // DISPLAY
pn->increment();
}
~String()
{
#ifdef DISPLAY
cout << "Delete String Obj." << endl;
#endif // DISPLAY
pn->decrement();
}
String(const String& s):pn(s.pn) //拷贝构造--构造的函数的一种重写形式
{
pn->increment();
}
String& operator = (const String & s) //赋值语句
{
if (this != &s) {
pn->decrement();
pn = s.pn; //pn指向新对象,若pn有原对象需要把原对象的计数器 -1.
pn->increment();
}
return *this;
}
public:
void to_upper() { //小写字母变大写 需要用写时拷贝--->更改数据的时候用深拷贝。
pn->decrement();
String_rep* new_pn = new String_rep(pn->m_data); //重新构造一个引用计数器,拷贝属于自己的数据和计数。
pn = new_pn;
pn->increment();
char* p = pn->m_data;
while (*p!= '\0') {
if (*p >= 'a' && *p <= 'z') {
*p -= 32;
}
p++;
}
}
private:
String_rep *pn; //引用计数器指针
};
ostream& operator<<(ostream& out, const String& s) {
out << s.pn->Getdata();
return out;
}
写时拷贝分析:
测试结果: