這裏向大家介紹C++庫函數中的一個文件<string.h> 。爲了簡化程序員的工作量並提高代碼的安全性和健壯性 C++標準庫找專門提供了<string>文件用來提供對字符串進行多種操作的庫函數接口,這些接口配合C++中<iostream>文件中的strlen strstr strcpy .......系列函數共同爲字符串的處理服務。
本文中模擬的string類沒有加迭代器 爲了簡化抽象出<string>文件中string類的功能、內部實現機制和設計思想 。
拷貝構造函數 operator=()中的淺拷貝與深拷貝String::String(const String& s)//淺拷貝:_str(s._str){}
void Test2(){String s1("hello");String s2(s1);}
這裏的s2 中的字符指針直接指向了s1中字符指針指向的字符串 。 對象s1析構之後 s2中指針指向的字符串已被釋放 再調用s2的析構函數導致同一空間被析構兩次程序崩潰。編譯器只是將指針的內容拷貝過來,導致多個對象共用一塊內存空間,當其中任意對象將這塊空間釋放之後,另外一些對象並不知道這塊空間已經還給了操作系統,以爲還有效,所以再對這塊空間進行操作時,造成了違規訪問。
爲解決淺拷貝問題只有進行深拷貝 即爲新對象重新開闢字符串空間讓新對象字符指針指向自己的字符串空間 該空間字符串內榮是源字符串的拷貝String(char* str = ""):_str(new char[strlen(str) + 1]){strcpy(_str, str);}
String& operator = (const String& s)//寫法1{if (this != &s){delete[] _str;_str = new char[strlen(s._str) + 1];strcpy(_str, s._str);}}
String& operator=(const String& s)//寫法2{if (this != &s){char* tmp = new char[strlen(s._str) + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;}return *this;//爲了支持鏈式訪問}
void Test2(){String s1("hello");String s2(s1);Srring s3;s3 = s2;}拷貝的對象s2中_str的值(字符串的地址)和s1對象中的_str的值不同,重新開闢了空間(即就是深拷貝)。
一般情況下,上面的兩種寫法都可以,但是相對而言,第二種更優一點。對於第一種,先釋放了舊空間,但是如果下面用new開闢新空間時有可能失敗——>拋異常,而這時你是將s2賦值給s3,不僅沒有賦值成功(空間開闢失敗),而且也破壞了原有的s3對象。對於第二種,先開闢新空間,將新空間的地址賦給一個臨時變量,就算這時空間開闢失敗,也不會影響原本s3對象。綜上:第二種方法更優一點。最後的返回值是爲了支持鏈式訪問。例如:s3 = s2 = s1;上面所寫的拷貝構造函數和賦值運算符重載函數屬於傳統寫法,下面我們一起來看看它們的現代寫法:String(const String& s):_str(NULL){String tmp(s._str);swap(_str, tmp._str);}String& operator = (const String& s){if (this != &s){String tmp(s._str);swap(_str, tmp._str);}return *this;}
下面兩幅圖介紹一下現代寫法拷貝構造函數和賦值運算符重載的思想:
從上面的深拷貝我們可以看出,相比淺拷貝,深拷貝的效率明顯較低,因爲每拷貝一個對象就需要開闢空間和釋放空間,再有就是賦值運算符重載也是一樣的需要重新開闢空間並釋放空間。假如有這樣的一種情況,拷貝和賦值得到的對象只用於”讀”,而不用於”寫”,那麼是不是就不需要重新開闢空間了呢?
string的引用計數指針版本:在對象中搞一個指向多個對象公共引用計數的指針 用以記錄該字符串空間被幾個對象的字符指針共同管理namespace COW1//寫時拷貝 引用計數指針{class String{public:String(const char* str = ""):_refCountPtr(new int(1)){//引用計數指針版本的String構造函數初始化標識字符串字符數的_size//字符串空間大小的_capacity//字符串_str//引用計數指針_refCountPtr指向的空間 並初始化爲一(同一個字符串最初只有一個對象管理)_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}String(const String& s)//引用計數方式的String類的拷貝構造函數:_refCountPtr(s._refCountPtr),_size(s._size),_capacity(s._capacity),_str(s._str){//每次做拷貝的時候只是讓新對象的字符指針和引用計數指針指向原對象空間//並++兩對象引用計數指針指向的共同引用計數 實際上內存中的字符串只有一份(*_refCountPtr)++;//這樣做是爲了節省空間 所謂“寫時拷貝 不拷就賺”}String& operator = (const String& s){if (_str != s._str){Ralese();_refCountPtr = s._refCountPtr;_str = s._str;(*_refCountPtr)++;}return *this;}void Ralese(){if (--(*_refCountPtr) == 0){cout << "delete[]" << _str << endl;delete[] _str;delete _refCountPtr;}}void PushBack(const char& ch){//修改字符串的時候 才真正拷貝一份新的在使用角度上已經有的新字符串//在該字符串上做修改CopyOnWrite();if (_size == _capacity){}_str[_size++] = ch;_str[_size] = '\0';}void CopyOnWrite(){if (*_refCountPtr > 1){//寫時拷貝發生的條件 即同一個在內存中的字符串被多個對象//的字符指針所引用char* newSize = new char[_capacity];strcpy(newSize,_str);*(_refCountPtr)--;_str = newSize;_refCountPtr = new int(1);//本函數負責修改公共引用計數 拷貝原對象所有內容}}char& operator[](size_t pos){CopyOnWrite(); //非const類型的operator[]函數因爲要修改_str[X]中的內容return _str[pos];//必須爲要修改的對象的字符串重新開闢空間 否則在應用層面上將形成//改變了多個字符串的現象 因爲在底層他們各自的char*指向同一字符串}char& operator[] (size_t pos)const{return _str[pos];}~String(){Ralese();}const char* c_str(){return _str;}private:char* _str;int* _refCountPtr;size_t _size;size_t _capacity;};// "有時候讀的時候也要拷貝"void TestString1(){/*String s1("hello world");String s2(s1);s1 = s2;String s3("dadadada");s3 = s1;String s4("dadadsqwqdq");s1 = s4;*/String s1("hello world");String s2(s1);cout << s1[0] << endl;cout << s1.c_str() << endl;cout << s2.c_str() << endl;}}以下是圖示思路:
使用指針可以完成引用計數的淺拷貝,但是因爲每構造一個對象,都需要開闢兩塊空間,這樣容易造成內存碎片。由new[]可以聯想到類似模型—->只開闢一塊空間(多開四個字節),把引用計數放在字符串首地址的前四個字節上。這樣不但解決了內存碎片問題,而且也可以程序的運行效率。
namespace COW2//寫時拷貝 在對象空間前4個字節存放引用計數{class String//在構造時初始化引用計數 涉及到拷貝時修改引用計數{public:String(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 5];strcpy(_str + 4, str);_str += 4;GetCountRef()++;}int& GetCountRef(){return *((int*)(_str - 4));}String(const String& s):_size(s._size),_capacity(s._capacity),_str(s._str){GetCountRef()++;}String& operator = (const String& s){if (_str != s._str){Ralese();_size = s._size;_capacity = s._capacity;_str = s._str;GetCountRef()++;}}void Ralese(){if (--GetCountRef() == 0){delete[] (_str - 4);}}void CopyOnwrite(){if (GetCountRef() > 1){char* newSize = new char[_capacity + 5];strcpy(newSize + 4, _str);GetCountRef()--;_str = newSize + 4;*((int*)(_str - 4)) = 1;}}char& operator[](size_t pos){CopyOnwrite();return _str[pos];}char operator[](size_t pos)const{return _str[pos];}~String(){Ralese();}const char* c_str(){return _str;}private:char* _str;size_t _size;size_t _capacity;};}關於string類中涉及的增刪查改工作:namespace DC//實現String類的增刪查改{class String{public:String(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char(_capacity + 1);strcpy(_str,str);}String(const String& s){String tmp(s._str);//拷貝構造時先創造一個臨時對象 讓臨時對象和當前Swap(tmp); //對象內容交換 再因爲臨時對象出函數作用域要被釋放 所以釋放對象//留下拷貝後的對象。}void Swap(String& tmp){swap(_str, tmp._str);swap(_size, tmp._size);swap(_capacity, tmp._capacity);}String& operator = (String& s){Swap(s);return *this;}size_t Size(){return _size;}size_t Capacity(){return _capacity;}char* c_str(){return _str;}void PushBack(char ch){if (_size == _capacity){Expand(_capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}void PushBack(const char* str){size_t len = strlen(str);if (_size + len > _capacity){Expand(_size + len);}strcpy(_str + len, str);}void Popback(){assert(_size);--_size;}void Insert(char ch, int pos){if (_size > _capacity){Expand(_capacity * 2);}int end = _size;while (end >= pos){_str[end+ 1] = _str[end];--end;}_str[pos] = ch;++_size;}//插入方法同順序表void Insert(const char* str, int pos){if (_size + strlen(str) > _capacity){Expand(_size + strlen(str));}int end = _size;while (end >= (int)pos){_str[end + strlen(str)] = _str[end];--end;}while (*str){_str[pos++] = *str++;}_size += strlen(str);}void Erase(size_t pos, size_t count){if (pos + count > _size - 1){_str[pos] = '\0';_size = pos;//pos 到 count超過整個字符串 刪去pos之後字符}else{strcpy(_str + pos, _str + pos + count);//將 pos 至 count 段的字符覆蓋掉_size -= count;}}void Expand(size_t n){if (n > _capacity){_str = (char*)realloc(_str, n + 1);assert(_str);_capacity = n;}}int Find(const char ch){for (size_t i = 0; i < _size; ++i){if (_str[i] == ch){return i;}else{return -1;}}}int Find(const char* str)const{assert(str);const char* srcStr = _str;const char* subStr = str;size_t srcIndex = 0;size_t subIndex = 0;size_t sublen = strlen(str);while (srcIndex < _size - sublen - 1){size_t matchIndex = srcIndex;//這裏是在一個疑似的第一個字符索引while (_str[matchIndex] == str[subIndex]){//該循環在疑似字串中一個個比對matchIndex++; //字符直到比到要找的字串結尾2subIndex++;if (subIndex == sublen){return srcIndex;}}subIndex = 0;//字串的索引重新置零 從下一個子串的頭開始找srcIndex++;//找以下一個字符爲首的字符串是不是字串str}return -1;}~String(){if (_str){delete[] _str;_str = NULL;_capacity = _size = 0;}}bool operator<(const String& s) const{size_t i = 0;for (; i < _size && i < s._size; ++i){if (_str[i] < s._str[i]){return true;}else if (_str[i] > s._str[i]){return false;}}if (i == _size && i < s._size) //?{return true;}else{return false;}}inline bool operator<=(const String& s) const{return *this < s || *this == s;}inline bool operator>(const String& s) const{return !(*this <= s);}bool operator>=(const String& s) const;bool operator==(const String& s) const{size_t i = 0;for (; i < _size && i < s._size; ++i){if (_str[i] != s._str[i]){return false;}}if (i == _size && i == s._size){return true;}else{return false;}}private:char* _str;size_t _size;size_t _capacity;};}