关于string类中的一些小问题的总结

在学习了深拷贝&浅拷贝&引用计数后,我简单的对本节课做了一个小总结:

1.什么是浅拷贝,里面存在什么问题?

     简单来说,浅拷贝就是对象的数据成员之间的简单赋值。

    在浅拷贝中,若在string类中没有给出拷贝构造函数和赋值运算符重载函数,则系统会默认生成如下拷贝构造和赋值运算符重载函数。

//拷贝构造函数
string(const string&s)
:_pstr(s.pstr)
{}
//赋值运算符重载
string &operator = (const string &s)
{
if (this != &s)
{
_pstr = s.pstr;
}
return *this;
}

   浅拷贝在赋值时,例如在string类中,进行strings2(s1)时,即用s1对s2进行赋值时,只是简单的将s1中所有的东西原模原样,原封不动的拷贝过来。会导致两个对象共用同一块空间,导致一块空间被释放两次,而原来的空间忘记释放,造成内存泄漏。

2.用深拷贝怎么解决,给出深拷贝的两种书写方式:普通版和简洁版 

   在深拷贝的实现中,用户可以自己给出拷贝构造函数:

普通版:

//拷贝构造函数
string(const string&s)
:_pstr(new char[strlen(s.pstr)+1])
{
strcpy(_pstr, s.pstr);
}

 //赋值运算符重载
string &operator = (const string &s)
{
if (this != &s)
{
char*pTemp = new char[strlen(s.pstr) + 1];
strcpy(pTemp, s.pTemp);
delect[]_pstr;
_pstr = pTemp;
}
return *this;
}

简单版:

//拷贝构造函数
string(const string&s)
{
string temp(s._pstr);
swap(_pstr, temp._pstr);
}
//赋值运算符重载
string &operator = (const string &s)
{
if (this != &s)
{
string temp(s._pstr);
swap(_pstr, temp._pstr);
}
return *this;
}


3.什么是引用计数,用引用计数能解决浅拷贝存在的问题吗? 怎么解决

  首先看一段代码:
class String
{
public:
String(const char *pStr = "")
:_pStr(new char[strlen(pStr) + 1])
, count(new int[1])
{
strcpy(_pStr, pStr);
*count = 1;
}
String(const String& s)
{
_pStr = s._pStr;
count = s.count;
(*count)++;
}
~String()
{
if (NULL != _pStr && *count == 1)
{
delete[] _pStr;
delete[] count;
_pStr = NULL;
count = NULL;
}
else if (*count > 1)
{
(*count)--;
}
}
private:
char *_pStr;
int *count;
};
void FunTest()
{
String s1("hello bit");
String s2(s1);
}
int main()
{
FunTest();
system("pause");
return 0;
}

   对于浅拷贝所出现的问题,不仅仅可以使用深拷贝来解决,我们可以通过标记储存字符串那段空间的使用次数,用来真正确定,这块空间是不是真的要释放!这就是引用计数法

引用计数,它是多个对象一同进行维护的。比如创建s1之后,s1的引用计数就为1,通过s1创建s2,s1和s2共用了s1的字符串。则s1和s2的引用计数要一致为2。为了实现,所以在成员变量定义一个int* 类型的指针,这个指针指向存储引用计数的空间,多个对象指向同一块的引用计数空间时,说明他们使用的同一字符串!

   创建对象s1,使用s1拷贝构造s2,可以看到他们使用的是同一字符串,同一引用计数。在函数返回之前,s2先销毁,发现s1也是用的是这块空间,所以就让引用计数减去1,不进行销毁。不过对于引用计数存在一个很重要的缺点,就是当多个对象使用同一个字符串时,任何一个对象修改字符串里面的值都会造成其他对象所指向的字符串改变。

4、对引用计数进行改进,完成string的引用计数版本。即写时拷贝

写时拷贝是什么?简单来说就是在多个对象共用字符串时,有一个对象需要修改字符串的内容,这个时候把字符串拷贝一份赋给这个对象,以免这个对象去修改其他对象所使用的字符串。

那么在什么时候才会出现写时拷贝呢?很显然,在共享同一块内存的类发生内容改变时,才会发生Copy-On-Write。比如string类的[]、=、+=、+、操作符赋值,还有一些string类中诸如insert、replace、append等成员函数,包括类的析构时。修改数据才会触发Copy-On-Write。写时拷贝是对引用计数法的一种优化,可以消除引用计数法中的错误!

在实现写时拷贝之前,我们先来对引用计数法进行一下优化,我们之前对引用计数和字符串是分了两块不同的内存存放,在我们实现的过程中不利于实现,所以我们将他们放在同一块内存中,引用计数占内存的前四个字节,后面存放字符串。

先写一个简单的写时拷贝小程序:

class String
{
public:
String(char *pStr = "")
:_pStr(new char[strlen(pStr) + 4 + 1])
{
_pStr += 4;
strcpy(_pStr, pStr);
*(int *)(_pStr - 4) = 1;
}
String(const String& s)
{
_pStr = s._pStr;
int len = Size();
(*(int *)(_pStr - 4))++;
}
~String()
{
if (NULL != _pStr)
{
int len = Size();
if (len == 1)
{
delete[](_pStr + 4);
_pStr = NULL;
}
else
{
(*(int *)(_pStr - 4))--;
}
}
}
void WriteCopy()//写时拷贝
{
int len = Size();
char *tmp = new char[len + 1 + 4];
tmp += 4;
strcpy(tmp, _pStr);
if (len > 1)
{
(*(int *)(_pStr - 4))--;
std::swap(_pStr, tmp);
(*(int *)(_pStr - 4)) = 1;
}
}
size_t Size()const
{
char* tmp = _pStr;
size_t count = 0;
while (*tmp++)
{
count++;
}
return count;
}
String operator =(const String & s)
{
_pStr = s._pStr;
int len = this->Size();
(*(int *)(_pStr - 4))++;
}
char& operator [](size_t idx)
{
static char sNULL = '\0';
if (idx < Size() && idx > 0)
{
WriteCopy();
return _pStr[idx];
}
return sNULL;
}
const char& operator [](size_t idx)const
{
static char sNULL = '\0';
if (idx < Size() && idx > 0)
{
return _pStr[idx];
}
return sNULL;
}

public:
char *_pStr;
};
void FunTest()
{
String s1("hello ");
cout << *(((int *)s1._pStr) - 1) << endl;
String s2(s1);
String s3 = s2;
cout << *((int *)s1._pStr - 1) << endl;
s3[2] = 'W';
cout << *((int *)s1._pStr - 1) << endl;
}
int main()
{
FunTest();
system("pause");
return 0;
}

先来分析分析代码:在程序中,先创建对象s1,然后通过s1创建s2,再创建s3通过s2赋值给s3来改变s3的内容,这个时候就会发生写时拷贝,s3会拥有新的内存空间,s1引用计数的变化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章