标签:c++中的写时拷贝问题以及一些string中的函数实现
要写string的函数,首先就是创建一个string的类,在实现string类的过程中一直不断优化,以减少代码量和考虑不周的问题,首先我先给出刚开始的经典写法
经典解法(初级程序员适用)
class String
{
public:
String(const char *str = ""): //构造函数
_str(new char[strlen(_str)+1])
{
strcpy(_str, str);
}
String(const String &str) //拷贝构造函数
{
_str = new char[strlen(str._str) + 1];
strcpy(_str, str._str);
}
~String() //析构函数
{
if (_str != NULL)
{
delete[]_str;
}
}
public:
String &operator=(const String &str)//‘=‘符号重载
{
if (this != &str)
{
delete[]_str;
_str = (char*)new char[strlen(str._str) + 1];
strcpy(_str, str._str);
}
return *this;
}
}
此写法看似解决了string类的定义问题,实则仔细查看此代码你会发现代码在开辟空间时并未检测开辟空间是否成功,若new开辟空间失败,_str的内容已经丢失,所以可以看出来好的代码必定是经过深思熟虑后写出来的,在写代码时一定要考虑各种边际情况和各种异常情况的处理,在我们发现上面的问题后作出修改,代码已不够简洁,进过优化后的代码如下:
class String
{
public:
String(char *str = ""):// 构造函数
_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
//使用swap,一致性
String(String &str) ://拷贝构造函数,注意必须给_str赋初值,否则_str是随机值,指向的是一块非法空间
_str(NULL)
{
String tmp(str._str);
swap(_str, tmp._str);
}
String &operator=(String str)// ‘=‘号重载,巧妙使用值传递,在函数结束时,调用析构函数,将_str之前的空间释放
{
swap(_str, str._str);
return *this;
}
~String() //析构函数
{
if (_str != NULL)
{
delete[]_str;
}
}
在上边的代码中,我们发现在使用swap后代码变得简洁,而且在‘=‘符号重载时也不会出现丢失_str内容的情况,特别注意在拷贝构造中,一定要给_str赋初值,否则在代码块结束会出现释放非法空间的情况,因为在_str未赋初值的情况下是一个随机值,也就是_str指向一块非法空间。
此时的写法依然不是最好的写法,因为在程序运行的过程中使用动态开辟所占用的时间的比较多,所以此时就有写时拷贝的概念:
在维基百科中是这么定义的,写入时复制(Copy-on-write)是一个被使用在程式设计领域的最佳化策略。其基础的观念是,如果有多个呼叫者(callers)同时要求相同资源,他们会共同取得相同的指标指向相同的资源,直到某个呼叫者(caller)尝试修改资源时,系统才会真正复制一个副本(private copy)给该呼叫者,以避免被修改的资源被直接察觉到,这过程对其他的呼叫只都是通透的(transparently)。此作法主要的优点是如果呼叫者并没有修改该资源,就不会有副本(private copy)被建立。
简单的来说,就是在程序运行的过程中,如果多个对象同时要求相同的内容,则系统会给他们指定相同的空间,直至其中一个对象需要修改内容时才会真正给这个对象复制一个副本,从而提高程序的运行效率。
代码如下:
class String //写时拷贝
{
public:
String(const char* str = ""):
_str(new char[strlen(str) + 1 + CAPACITY+4]) //多开辟四个字节存放空间使用次数
, _capacity(strlen(str) + 1 + CAPACITY)
, _size(strlen(str))
{
*(int *)_str = 1;
_str = _str + 4;
strcpy(_str, str);
}
String(const String &str)
:_str(str._str)
{
++*(PointFirst(_str));
_capacity = str._capacity;
_size = str._size;
}
~String()
{
if (--*PointFirst(_str) == 0) //当使用相同空间的对象最后一次析构时,释放空间
{
delete[](_str - 4);
}
}
}
上面的代码和前面的不同处是在开辟空间时多开辟了四个字节去保存空间的使用次数,每次拷贝构造时加一,在析构时也是在保存的这个值为0时才会释放空间。
我们验证下写实拷贝和经典拷贝的效率差异
void Test()
{
String str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
int i = 0;
time_t start = time(NULL);
for (; i < 20000000; i++)
{
String stri(str);
}
time_t end = time(NULL);
cout << (end - start) << endl;
}//使用普通拷贝构造函数,用时25s,使用写实拷贝只需要3s,效率一目了然。
在下面我再将自己写的一些使用写实拷贝的一些简单的string函数贴出来:
代码如下:
public:
String &operator=(const String &str)
{
if (this != &str)
{
if (--(*PointFirst(_str)) == 0) //当此对象独立使用一块空间时,在赋值前释放这块空间
{
delete[]PointFirst(_str);
}
_str = str._str;
++*PointFirst(str._str);
}
return *this;
}
char &operator[](int index)
{
if ((*PointFirst(_str)-1) == 0)
{
return _str[index];
}
--*PointFirst(_str);
StringCopyWrite();
return _str[index];
}
public:
int Strcmp(const String &str)
{
char *pstr1 = _str;
char *pstr2 = str._str;
while (*pstr1 == *pstr2)
{
pstr1++;
pstr2++;
if (*pstr1 == ‘\0‘&&*pstr2 == ‘\0‘)
{
return 0;
}
}
if (*pstr1 > *pstr2)
{
return 1;
}
return -1;
}
bool operator==(const String &str)
{
if (Strcmp(str) == 0)
{
return true;
}
return false;
}
bool operator>(const String &str)
{
if (Strcmp(str) == 1)
{
return true;
}
return false;
}
bool operator<(const String &str)
{
if (Strcmp(str) == -1)
{
return true;
}
return false;
}
//写时拷贝函数
void insert(int pos, char ch)
{
if (--*PointFirst(_str) != 0) //和其他类成员变量公用一块空间
{
StringCopyWrite();
}
CheckCapacity(Strlen(_str) + 2);
int index = _size;
int sz = Strlen(_str);
while (sz >= pos)
{
_str[index - 1] = _str[sz];
index--;
sz--;
}
_str[pos] = ch;
}
void insert(int pos, char *str)
{
if (--*PointFirst(_str) != 0)
{
StringCopyWrite();
}
CheckCapacity(Strlen(_str) + Strlen(str) + 1);
int sz = Strlen(_str);
int index = _size;
while (sz >= pos)
{
_str[index - 1] = _str[sz];
index--;
sz--;
}
while (*str != ‘\0‘)
{
_str[pos++] = *str++;
}
}
void insert(int pos, const String &str)
{
if (--*PointFirst(_str) != 0)
{
StringCopyWrite();
}
this->insert(pos, str._str);
}
void PushBack(char ch)
{
if (--*PointFirst(_str) != 0)
{
StringCopyWrite();
}
int sz = size();
this->insert(sz - 1, ch);
}
int size()//返回值即为字符串长度(包含‘\0‘)
{
char *p = _str;
int size = 0;
while (*p != ‘\0‘)
{
size++;
p++;
}
return ++size;
}
void CheckCapacity(int sz)
{
if (sz >= _capacity)
{
_capacity = sz + CAPACITY;
}
_size = sz;
char *tmp = new char[_capacity + 4];
*(int *)tmp = *PointFirst(_str);
tmp += 4;
Strcpy(tmp, _str);
swap(tmp, _str);
delete[]PointFirst(tmp);
}
void StringCopyWrite()
{
char *tmp = _str;
_str = new char[Strlen(_str) + 5];
_str += 4;
Strcpy(_str, tmp);
*PointFirst(_str) = 1;
}
private:
char *_str;
int _size;
int _capacity;
};
在使用动态开辟的程序中,非常容易造成内存泄漏,很多程序的问题都处在释放动态内存处,这也是c++中比较难的一个知识点,只有能熟练使用动态内存开辟,才能将c++的魅力体现出来。
随后再更........
本文出自 “qin-wang” 博客,请务必保留此出处http://10810196.blog.51cto.com/10800196/1749747
标签:c++中的写时拷贝问题以及一些string中的函数实现
原文地址:http://10810196.blog.51cto.com/10800196/1749747