string 的深浅拷贝
class String
{
public:
// 1. 构造
String(const char *str = " ")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
// 2. 拷贝构造
// s2(s1)
String(const String &str)
:_str(str._str)
{}
// 3. 赋值操作符重载
// s2 = s1
String& operator=(const String &str)
{
if(this != &str)
{
_str = str._str;
}
return *this;
}
// 4. 析构
~String()
{
if(_str)
delete[] _str;
}
// 5. C 风格字符串
const char *c_str()
{
return _str;
}
// 6. 写时拷贝
void CopyOnWrite();
// 7. 随机访问
char &operator[](size_t pos)
{
return _str[pos];
}
private:
char *_str;
};
当类里面有指针对象时,如果进行简单赋值的浅拷贝,使两个对象指向同一块内存,程序就会存在崩溃的问题
void TestString()
{
String s1("hello, world");
String s2 = s1;
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
}
Error in
./main
: double free or corruption (fasttop): 0x0000000000c27010
这里因为是浅拷贝 , 所以 s1 和 s2 是指向同一块空间的 , 所以当 s1 的生命周期结束时调用析构函数已经把这块空间释放了 , 当 s2 的生命周期结束时 , 再调用析构函数 , 就成了 double free
, 程序就崩溃了
为了解决这个问题 , 我们就需要用 深拷贝
深拷贝就是 拷贝 s2 时重新开一块和 s1 一样大的空间 , 并将 s1 的值拷贝下来 , 这样 s1 和 s2 指向各自的空间 , 析构时释放各自的空间
// 二. 深拷贝
class String
{
public:
// 1. 构造
String(const char *str = " ")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
// 2. 拷贝构造
// s2(s1)
String(const String &str)
:_str(new char[strlen(str._str) + 1])
{
strcpy(_str, str._str);
}
// 3. 赋值操作符重载
// s2 = s1
String& operator=(const String &str)
{
if(this != &str)
{
// 深拷贝
char* tmp = new char[strlen(str._str) + 1];
strcpy(tmp, str._str);
delete[] _str;
_str = tmp;
}
return *this;
}
// 4. 析构
~String()
{
if(_str)
{
delete[] _str;
_str = NULL;
}
}
// 5. C 风格字符串
const char *c_str()
{
return _str;
}
// 6. 随机访问
char &operator[](size_t pos)
{
return _str[pos];
}
private:
char *_str;
};
但是每次都深拷贝 , 消耗太大了 , 效率不高 , 于是我们又想到了一种方法 - 引用计数
设置一个引用计数器 , 每次拷贝的时候 , 引用计数器加一 , 以表示当前有多少个对象指向这块空间 , 析构的时候先判断引用计数 , 如果引用计数大于 1 , 那么表示还有别人正在用这块空间 , 此时只用把引用计数减一 , 等到引用计数为 1 的时候 , 才需要真正释放这块空间
那么如何设置引用计数器呢 ?
private:
char *_str;
int _count;
这种方法是不行的 , 因为这样每个对象都有一个独立的计数器 , 而不是共享的 , 就起不到引用计数的作用了
private:
char *_str;
int* _pcount;
这样就可以了 , 设置一个指针 , 让每个对象的这个指针都指向同一块空间 , 对这块空间上的值进行修改 , 从而达到引用计数的效果
~String()
{
if(--(*m_pcount) == 0)
{
delete[] m_str;
delete m_pcount;
}
}
每次调用析构函数时 , 先检查引用计数 , 如果引用计数为 1 了 , 才真正释放空间 , 否则只用把引用计数减一就好了
引用计数解决了析构时空间被多次释放的问题 , 但是如果要对拷贝对象的值进行修改的话 ,
还需要用到 写时拷贝
String s1("hello");
String s2 = s1;
cout << s1.C_str() << endl;
cout << s2.C_str() << endl;
s2[0] = 'x';
cout << s1.C_str() << endl;
cout << s2.C_str() << endl;
修改 s2 的值也会影响 s1 , 但其实他们俩指向同一块空间 , 就是同一个东西 , 修改一个 , 另一个必定受影响 . 前面说了深拷贝可以解决这个问题 , 但是还有一种更好的方法 - 写时拷贝
void CopyOnWrite()
{
if(*m_pcount > 1)
{
char *tmp = new char[strlen(m_str) + 1];
strcpy(tmp, m_str);
--(*m_pcount);
m_str = tmp;
m_pcount = new int(1);
}
}
char &operator[] (size_t pos)
{
CopyOnWrite();
return m_str[pos];
}
在需要对对象的值进行修改的时候 , 再开空间拷贝 , 可以节省空间 , 提高效率
void TestCopyOnWrite()
{
TEST_HEAD;
cow::String s1("hello");
cow::String s2 = s1;
cout << s1.C_str() << endl;
cout << s2.C_str() << endl;
s2[0] = 'x';
cout << s1.C_str() << endl;
cout << s2.C_str() << endl;
}
这样修改 s2 的值就不会影响 s1 了
其实 , 不光是在 “写” 的时候会拷贝 , “读” 的时候也会拷贝 , 因为 []
是一个可读可写的接口
完整代码
#include <iostream>
#include <string.h>
#include <assert.h>
#include <stdio.h>
using namespace std;
#define TEST_HEAD printf("=====%s=====\n", __FUNCTION__)
// 实现两种方式的引用计数, 写时拷贝。
// 一. 浅拷贝
/*
class String
{
public:
// 1. 构造
String(const char *str = " ")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
// 2. 拷贝构造
// s2(s1)
String(const String &str)
:_str(str._str)
{}
// 3. 赋值操作符重载
// s2 = s1
String& operator=(const String &str)
{
if(this != &str)
{
_str = str._str;
}
return *this;
}
// 4. 析构
~String()
{
if(_str)
delete[] _str;
}
// 5. C 风格字符串
const char *c_str()
{
return _str;
}
// 6. 写时拷贝
void CopyOnWrite();
// 7. 随机访问
char &operator[](size_t pos)
{
return _str[pos];
}
private:
char *_str;
};
*/
// 二. 深拷贝1.0
/*
class String
{
public:
// 1. 构造
String(const char *str = " ")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
// 2. 拷贝构造
// s2(s1)
String(const String &str)
:_str(new char[strlen(str._str) + 1])
{
strcpy(_str, str._str);
}
// 3. 赋值操作符重载
// s2 = s1
String& operator=(const String &str)
{
if(this != &str)
{
// 深拷贝
char* tmp = new char[strlen(str._str) + 1];
strcpy(tmp, str._str);
delete[] _str;
_str = tmp;
}
return *this;
}
// 4. 析构
~String()
{
if(_str)
{
delete[] _str;
_str = NULL;
}
}
// 5. C 风格字符串
const char *c_str()
{
return _str;
}
// 6. 写时拷贝
void CopyOnWrite();
// 7. 随机访问
char &operator[](size_t pos)
{
return _str[pos];
}
private:
char *_str;
};
*/
// 三. 深拷贝2.0
class String
{
public:
// 1. 构造
String(const char *str = " ")
: _size(strlen(str))
, _capacity(_size)
{
_str = new char[_size + 1];
strcpy(_str, str);
}
void Swap(String &s)
{
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
}
// 2. 拷贝构造
// s2(s1)
String(const String &s)
: _str(NULL)
{
String tmp(s._str);
this->Swap(tmp);
}
// 3. 赋值操作符重载
// s2 = s1
String &operator= (String &s)
{
this->Swap(s);
return *this;
}
// 4. 析构
~String()
{
if(_str)
{
delete[] _str;
_str = NULL;
}
}
// 5. 返回 C 字符串
const char *c_str()
{
return _str;
}
// 6. 写时拷贝
void CopyOnWrite();
// 7. 随机访问, 重载 []
char &operator[] (size_t pos)
{
return _str[pos];
}
// 8. 扩容
void Expand(size_t n)
{
if(n > _capacity)
{
char *tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
// 9. 预先开空间
void Reserve(size_t n)
{
Expand(n);
}
// 10. 调整大小
void Resize(size_t n, char ch = '\0')
{
if(n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if(n > _capacity)
{
Expand(n);
}
for(size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
}
// 11. 任意位置插入字符
void Insert(size_t pos, char ch)
{
assert(pos <= _size);
if(_capacity == _size)
{
Expand(_capacity * 2);
}
int end = _size;
while(end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}
_str[pos] = ch;
++_size;
}
// 12. 在任意位置插入字符串
void Insert(size_t pos, const char *str)
{
assert(pos <= _size);
int len = strlen(str);
if(_size + len >= _capacity)
{
Expand(_size + len);
}
int end = _size;
while(end >= (int)pos)
{
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
}
// 13. 尾插
void PushBack(char ch)
{
Insert(_size, ch); // 调在任意位置插入字符的 Insert
}
// 14. 添加字符串
void Append(const char *str)
{
Insert(_size, str); // 调在任意位置插入字符串的 Insert
}
// 15. 重载 +=
// s1 += "hello"
String &operator+= (const char *str)
{
this->Append(str);
return *this;
}
// s1 += s2
String &operator+= (const String &s)
{
*this += s._str;
return *this;
}
// 16. 重载 +
// s1 + "hello"
String operator+ (const char *str)
{
String ret(*this);
ret.Append(str);
return ret;
}
// s1 + s2
String operator+ (const String &s)
{
return *this + s._str;
}
// 17. 尾删
void PopBack()
{
_str[--_size] = '\0';
}
// 18. 从 pos 位置开始删除 len 个字符
void Erase(size_t pos, size_t len)
{
assert(pos <= _size);
if(pos + len > _size)
{
_str[_size] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
// 19. 求 size
size_t Size()
{
return _size;
}
// 20. 求 capacity
size_t Capacity()
{
return _capacity;
}
// 21. 判空
bool Empty()
{
return _size == 0;
}
// 22. 查找字符
size_t Find(char ch) const
{
for(size_t i = 0; i < _size; i++)
{
if(ch == _str[i])
return i;
}
return npos;
}
// 23. 查找字符串
size_t Find(const char *sub) const
{
char *src = _str;
while(*src)
{
const char *src_tmp = src;
const char *sub_tmp = sub;
while( *sub_tmp && *src_tmp == *sub_tmp )
{
++src_tmp;
++sub_tmp;
}
if(*sub_tmp == '\0')
{
return src - _str;
}
else
{
++src;
}
}
return npos;
}
// 24. 字符串比大小 重载 <
bool operator< (const String &s) const
{
const char *str_left = _str;
const char *str_right = s._str;
while(*str_left && *str_right)
{
if(*str_left < *str_right)
{
return true;
}
else if(*str_left > *str_right)
{
return false;
}
else
{
++str_left;
++str_right;
}
}
if(*str_left == '\0' && *str_right != '\0')
{
return true;
}
else
{
return false;
}
}
// 25. 判断字符串是否相等 重载 ==
bool operator== (const String &s) const
{
const char *str_left = _str;
const char *str_right = s._str;
while(*str_left && *str_right)
{
if(*str_left != *str_right)
{
return false;
}
else
{
++str_left;
++str_right;
}
}
if(*str_left == '\0' && *str_right == '\0')
{
return true;
}
else
{
return false;
}
}
// 重载 <=
bool operator<= (const String &s) const
{
return (*this < s) || (*this == s);
}
// 重载 >
bool operator> (const String &s) const
{
return !(*this <= s);
}
// 重载 >=
bool operator>= (const String &s) const
{
return !(*this < s);
}
// 重载 !=
bool operator!= (const String &s) const
{
return !(*this == s);
}
private:
char *_str;
size_t _size;
size_t _capacity;
public:
static size_t npos;
};
size_t String::npos = -1;
// 四. 引用计数, 写时拷贝
namespace cow
{
class String
{
public:
// 1. 构造
String(const char *str = " ")
{
m_str = new char[strlen(str) + 1];
strcpy(m_str, str);
m_pcount = new int(1);
}
// 2. 拷贝构造
// s2(s1)
String(const String &s)
{
m_str = s.m_str;
m_pcount = s.m_pcount;
++(*m_pcount); // 引用计数加一
}
// 3. 赋值操作符的重载
// s2 = s1
String &operator= (const String &s)
{
if(m_str != s.m_str)
{
if(--(*m_pcount) == 0)
{
delete[] m_str;
delete m_pcount;
}
m_str = s.m_str;
m_pcount = s.m_pcount;
++(*m_pcount);
}
return *this;
}
// 4. 析构
~String()
{
if(--(*m_pcount) == 0)
{
delete[] m_str;
delete m_pcount;
}
}
// 5. 写时拷贝
void CopyOnWrite()
{
if(*m_pcount > 1)
{
char *tmp = new char[strlen(m_str) + 1];
strcpy(tmp, m_str);
--(*m_pcount);
m_str = tmp;
m_pcount = new int(1);
}
}
// 6. 随机访问 重载 []
char &operator[] (size_t pos)
{
CopyOnWrite();
return m_str[pos];
}
// 读时不拷贝
const char &operator[] (size_t pos) const
{
cout << "const []" << endl;
return m_str[pos];
}
char *C_str()
{
return m_str;
}
private:
char *m_str;
int *m_pcount; // 引用计数器
size_t m_size;
size_t m_capacity;
};
}
void TestCopyConstructor()
{
String s1("hello");
String s3("world");
cout << s1.c_str() << endl;
cout << s3.c_str() << endl;
s1 = s3;
cout << s1.c_str() << endl;
cout << s3.c_str() << endl;
String s2(s1);
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
}
void TestCopyOnWrite()
{
TEST_HEAD;
cow::String s1("hello");
cow::String s2 = s1;
const cow::String s3(s1);
cout << s1.C_str() << endl;
cout << s2.C_str() << endl;
// cout << s3.C_str() << endl;
// s2[0] = 'x';
cout << s3[1] << endl;
cout << s1.C_str() << endl;
cout << s2.C_str() << endl;
}
void TestString()
{
TEST_HEAD;
String s1("hello, world");
String s2 = s1;
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
s1.PushBack('a');
s1.PushBack('b');
s1.PushBack('c');
cout << s1.c_str() << endl;
s1 += "hahahaha";
cout << s1.c_str() << endl;
s1.PopBack();
s1.PopBack();
s1.PopBack();
cout << s1.c_str() << endl;
s1.Append("zxc");
cout << s1.c_str() << endl;
s1.Resize(30, 'a');
cout << s1.c_str() << endl;
cout << s1.Size() << endl;
cout << s1.Capacity() << endl;
cout << s1.Find('a') << endl;
cout << (int)s1.Find("ccc") << endl;
s1.Erase(5, 10);
cout << s1.c_str() << endl;
bool ret = s1 < s2;
cout << ret << endl;
}
void TestNoCount()
{
TEST_HEAD;
String s1("111111111111111");
int begin = clock();
for(int i = 0; i < 1000000; i++)
{
String s2(s1);
}
int end = clock();
cout << end - begin << endl;
}
void TestCount()
{
TEST_HEAD;
cow::String s1("111111111111111");
int begin = clock();
for(int i = 0; i < 1000000; i++)
{
cow::String s2(s1);
}
int end = clock();
cout << end - begin << endl;
}
int main()
{
TestString();
TestCopyOnWrite();
TestNoCount();
TestCount();
TestCopyConstructor();
return 0;
}