标签:string 写时拷贝
String的实现需要注意的是String的拷贝构造。它的拷贝构造有深拷贝和浅拷贝之分。
我们先来用浅拷贝实现String
class String { public: String() { str = new char(‘A‘); } String(char *s) { str = new char[strlen(s) + 1]; if (str != NULL) { strcpy(str, s); } } String(const String& s) { str=s.str; } String& operator=(const String& s) { if (this != &s) { str=s.str; } return *this; } private: char *str; }; void test() { String s1("hello world"); String s2(s1); }
当s1,s2出自的作用域时,会自动调用析构函数,此时s1,s2指向同一片内存。所以这块内存会被释放两次,程序会崩溃。
所以在这里我们要采用深拷贝的方式
构造函数和赋值运算符重载
String(const String& s) { str = new char[strlen(s.str) + 1]; //new出来一块新的空间 if (str) { strcpy(str, s.str); } } String& operator=(const String& s) { if (this != &s) { if (str != NULL) { delete[] str; str = new char[strlen(s.str) + 1]; strcpy(str, s.str); } } return *this; }
还有一种方法可以解决这个一块空间会被多次释放的问题,那就是写时拷贝
在第一次构造一个对象的时候,多开辟四个字节当做计数器,用来记录有几个指针指向这块空间。每当用这块空间拷贝构造一个新对象或者把这块空间赋给另外一个对象时,计数器相应增加。那么当调用析构函数时,每次计数器减一,当计数器减到一时,说明只有一个指针指向这块空间,此时再把这块空间delete,就不会出现一块空间多次释放的问题了。
class String { public: String(char *str="") :_str(new char[strlen(str)+1+4]) { *((int *)_str) = 1; //将开辟的空间前4个字节强制转换成整型并赋为1 _str = _str + 4; //将_str重新定为真正字符串开始的地方,这样比较方便 strcpy(_str, str); //不用每次向后找 } String(const String& str) { _str = str._str; (*((int *)(_str - 4)))++; } ~String() { if (*_str != NULL) { if (((*((int *)(_str - 4)))--) == 0) //判断计数器是否减到0 { delete[] (_str-4); } } } public: String& operator=(const String& str) { if (this != &str) { if (((*((int *)(_str - 4)))--) == 0) { delete[] (_str-4); } _str = str._str; (*(int *)(str._str - 4))++; return *this; } } char& operator[](int index) { char *tmp = _str; if (((*(int *)(_str - 4))-1) != 0) { (*(int *)(_str - 4))--; _str = new char[strlen(_str) + 5]; (*(int *)(_str + 4)) = 1; _str = _str - 4; strcpy(_str, tmp); } return _str[index]; } private: char *_str; };
但是这样做也有一个坏处。就是指向同一块空间的指针,只要改一个指针指向的内容,等于其他的指针指向的内容也跟着改变了。
标签:string 写时拷贝
原文地址:http://10797037.blog.51cto.com/10787037/1761017