標(biāo)準(zhǔn)庫類型string表示可變長的字符序列,使用string類型必須首先包含它的頭文件。 作為標(biāo)準(zhǔn)庫的一部分,string定義在命名空間std中。
#include<string>//注意這里沒有.husing namespace std;下面讓我們一起來模擬string類中的幾個比較重要的成員函數(shù),一步一步的剖析它們的實現(xiàn)機(jī)制:
1.構(gòu)造函數(shù):
示例①
class String{public : String(char *str = "")//construction function :_str(str) {} ~String() { if (NULL != _str) { delete[] _str; } }PRivate : char *_str;};void Test2(){ String s1("hello"); String s2(new char[3]);}
從監(jiān)視窗口可以看出,s1貌似構(gòu)建成功了,但是在Test2()快要結(jié)束時,程序崩潰了,我們知道,析構(gòu)函數(shù)是在對象銷毀前執(zhí)行的, 所以程序崩潰的原因就是執(zhí)行析構(gòu)函數(shù)的時候,析構(gòu)函數(shù)中釋放對象中指針_str指向的空間, 但是S1中指針_str指向的是一個常量字符串—->存放在代碼區(qū)(c語言)或者說常量區(qū)(操作系統(tǒng)),而delete[]釋放的空間必須要是動態(tài)分配的,至此我們就可知程序崩潰的原因。
經(jīng)過上例的討論,我將構(gòu)造函數(shù)做了些許的修改: 示例②:
class String{public : String(char *str = "")//構(gòu)造函數(shù) { if (NULL == str) { _str = new char[1];//為了和delete[]配合使用 *_str = '/0'; } else { _str = new char[strlen(str) + 1]; strcpy(_str, str); } } ~String() { if (NULL != _str) { delete[] _str; } }private : char *_str;};構(gòu)造函數(shù)的參數(shù)列表帶有默認(rèn)值也是為了和類庫中的一致,當(dāng)以string s1;形式構(gòu)造對象時,希望構(gòu)造出的字符串為空串(含有’/0’)。
2.拷貝構(gòu)造函數(shù):
①淺拷貝:
String::String(const String& s)//淺拷貝:_str(s._str){}測試:
void Test2(){ String s1("hello"); String s2(s1);}上面的程序會崩潰,下面我們來分析原因: 
可以看出這里的s2采用了淺拷貝,而析構(gòu)的時候,先析構(gòu)的是s2,析構(gòu)s2時會釋放掉“hello”這塊空間,接下來再釋放s1時,程序就會崩潰,因為這塊空間被釋放了兩次。
淺拷貝:又稱為位拷貝,編譯器只是將指針的內(nèi)容拷貝過來,導(dǎo)致多個對象共用一塊內(nèi)存空間,當(dāng)其中任意對象將這塊空間釋放之后,另外一些對象并不知道這塊空間已經(jīng)還給了操作系統(tǒng),以為還有效,所以再對這塊空間進(jìn)行操作時,造成了違規(guī)訪問。
為了解決這個問題,我們引入了深拷貝(給要拷貝構(gòu)造的對象重新分配空間):

代碼如下:
String::String(const String& s)//深拷貝:_str(new char[strlen(s._str) + 1]){ strcpy(_str, s._str);}測試:
void Test2(){ String s1("hello"); String s2(s1);}
由監(jiān)視窗口可知,拷貝的對象s2中_str的值(字符串的地址)和s1對象中的_str的值不同,說明是重新開辟了空間(即就是深拷貝)。
3.賦值運(yùn)算符重載:
//方法①String& String::Operator=(const String& s){ if (this != &s) { delete[] _str; _str = new char[strlen(s._str) + 1]; strcpy(_str, s._str); } return *this;//為了支持鏈?zhǔn)皆L問}//方法②(優(yōu))String& String::operator=(const String& s){ if (this != &s) { char* tmp = new char[strlen(s._str) + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; } return *this;//為了支持鏈?zhǔn)皆L問}測試: 
一般情況下,上面的兩種寫法都可以,但是相對而言,第二種更優(yōu)一點。 對于第一種,先釋放了舊空間,但是如果下面用new開辟新空間時有可能失敗——>拋異常,而這時你是將s2賦值給s3,不僅沒有賦值成功(空間開辟失敗),而且也破壞了原有的s3對象。
對于第二種,先開辟新空間,將新空間的地址賦給一個臨時變量,就算這時空間開辟失敗,也不會影響原本s3對象。
綜上:第二種方法更優(yōu)一點。
最后的返回值是為了支持鏈?zhǔn)皆L問。 例如:s3 = s2 = s1;
上面所寫的拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載函數(shù)屬于傳統(tǒng)寫法,下面我們一起來看看它們的現(xiàn)代寫法:
拷貝構(gòu)造的現(xiàn)代寫法: 
賦值運(yùn)算符重載函數(shù)的兩種現(xiàn)代寫法:
 在面試時,一般寫出上面四個string類的成員函數(shù)即可,除非面試官特別要求。
從上面的深拷貝我們可以看出,相比淺拷貝,深拷貝的效率明顯較低,因為每拷貝一個對象就需要開辟空間和釋放空間,再有就是賦值運(yùn)算符重載也是一樣的需要重新開辟空間并釋放空間。 假如有這樣的一種情況,拷貝和賦值得到的對象只用于”讀”,而不用于”寫”,那么是不是就不需要重新開辟空間了呢?
下面讓我們一起來了解一下引用計數(shù)的淺拷貝:
我們需要使用一個變量可以標(biāo)記同一塊空間同時有幾個對象在使用—–>為了析構(gòu)(當(dāng)標(biāo)記的這個變量為1時,說明只有一個對象在使用這塊空間,那么在使用完成后就可以去釋放空間,否則讓這個計數(shù)的變量減1)。 引用計數(shù)的淺拷貝:
首先,我們需要討論下這個所謂的計數(shù)變量: ①使用數(shù)據(jù)成員 int _count; 在構(gòu)造函數(shù)中使得_count = 1; 在拷貝構(gòu)造函數(shù)中_count(++s._count). 但是析構(gòu)的時候無法改變每個對象中的_count的值。
String.h
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;class String{public: String(const char* str = ""); String(String& s); String& operator=(const String& s); ~String();private: int& GetRef(char *str); void release(); char *_str; int _count;};String.cpp
String::String(const char* str ){ if (NULL == str) { _str = new char[1]; *_str = '/0'; } else { _str = new char[strlen(str) + 1]; strcpy(_str, str); } _count = 1;}String::String(String& s):_str(s._str), _count(++(s._count)){ strcpy(_str, s._str);}String::~String(){ if (--_count == 0 && NULL != _str)//_count是每個對象的,減去一個共享一塊空間的其中一個對象的成員,并不會改變其他對象的成員變量_count的值 { cout << this << endl; delete[] _str; }}所以,使用數(shù)據(jù)成員變量—–>pass
②使用靜態(tài)數(shù)據(jù)成員:static int _count; 構(gòu)造函數(shù)內(nèi):_count =1; 拷貝函數(shù)內(nèi)部:_count++; 當(dāng)出現(xiàn)情況:String s1; String s2(s1); String s3(s2); 這時的引用計數(shù)_count = 3; 但是當(dāng)重新構(gòu)造一個對象時: String s4;——–>使得該類所有的引用計數(shù)_count = 1;
所以:靜態(tài)數(shù)據(jù)成員也不可以。 下面詳細(xì)講解:
String.h
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;class String{public: String(const char* str = ""); String(String& s); String& operator=(const String& s); ~String();private: int& GetRef(char *str); void release(); char *_str; static int _count;};String.cpp
String::String(const char* str ){ if (NULL == str) { _str = new char[1]; *_str = '/0'; } else { _str = new char[strlen(str) + 1]; strcpy(_str, str); } _count = 1;}String::String(String& s):_str(s._str){ strcpy(_str, s._str); _count++;}String::~String(){ if (--_count == 0 && NULL != _str) { cout << this << endl; delete[] _str; }}int String:: _count = 0;void Test1(){ String s1("hello"); String s2(s1); String s3(s2);}

從上圖可以看出靜態(tài)成員變量是這個類所有的對象所共享的,所以當(dāng)只是用一個對象去拷貝另一個對象時,這樣不會出錯,而且貌似也達(dá)到我們的要求了,可是當(dāng)重新構(gòu)造一個對象時,就會發(fā)現(xiàn)所有對象中的_count都修改為了1。
所以,使用靜態(tài)成員(static int count)————>pass
③使用指針: 使得拷貝的對象共用指向一個引用計數(shù)。 
賦值運(yùn)算符重載函數(shù)中的兩種情況: 第一種情況: 



由監(jiān)視窗口的數(shù)據(jù)可得,使用指針可以完成引用計數(shù)的淺拷貝,但是因為每構(gòu)造一個對象,都需要開辟兩塊空間,這樣容易造成內(nèi)存碎片。
由new[]可以聯(lián)想到類似模型—->只開辟一塊空間(多開四個字節(jié)),把引用計數(shù)放在字符串首地址的前四個字節(jié)上。 這樣不但解決了內(nèi)存碎片問題,而且也可以程序的運(yùn)行效率。
引用計數(shù)的淺拷貝的最優(yōu)解決方案: (這里我就不一一分析了,思路和上面使用指針是一樣的)

引用計數(shù)的淺拷貝同樣存在缺陷—>當(dāng)幾個共用同一塊空間的對象中的任一對象修改字符串中的值,則會導(dǎo)致所有共用這塊空間的對象中的內(nèi)容被破壞掉。
由此—->引入了寫時拷貝(Copy On Write) 顧名思義,寫時拷貝—>寫的時候進(jìn)行拷貝(深拷貝)
//String.h#pragma once#include<iostream>using namespace std;#include<assert.h>class String{public: String(const char* str = "")//構(gòu)造函數(shù) { if (NULL == str) { char* pTemp = new char[1 + 4]; _pStr = pTemp + 4; _GetRefer() = 1; *_pStr = '/0'; } else { char* pTemp = new char[strlen(str) + 1 + 4]; _pStr = pTemp + 4; strcpy(_pStr, str); _GetRefer() = 1; } } String(const String& s) :_pStr(s._pStr) { _GetRefer()++; } ~String() { Release(); } String& operator=(const String& s); char& operator[](size_t index); const char& operator[](size_t index)const; int& String::_GetRefer(); void Release(); friend ostream& operator<<(ostream& _cout, const String& s);private: char* _pStr;};//String.cpp#define _CRT_SECURE_NO_WARNINGS 1#include"String3.h"int& String::_GetRefer()//獲取引用計數(shù){ return *((int*)_pStr - 1);}void String::Release()//釋放空間{ if (--_GetRefer() == 0) { _pStr -= 4; delete[] _pStr; _pStr = NULL; }}String& String::operator=(const String& s){ if (_pStr != s._pStr) { if (--_GetRefer() == 0)//需要考慮兩種情況 { Release(); _pStr = s._pStr; _GetRefer()++; } else { _pStr = s._pStr; _GetRefer()++; } } return *this;}char& String::operator[](size_t index){ if (_GetRefer() > 1) { _GetRefer()--; char* pTemp = new char[strlen(_pStr) + 1 + 4]; pTemp += 4; strcpy(pTemp, _pStr); _pStr = pTemp; _GetRefer() = 1;//新開的空間 } return _pStr[index];}const char& String::operator[](size_t index)const{ return _pStr[index];}void Test1(){ const String s1("hello"); String s2(s1); //String s3; //s3 = s2; cout << s1[4] << endl;}int main(){ Test1(); return 0;}[]重載運(yùn)算符需要成對重載:char& String::operator[](size_t index){ if (_GetRefer() > 1) { char* pTemp = new char[strlen(_pStr) + 1 + 4]; pTemp += 4; strcpy(pTemp, _pStr); _GetRefer()--; _pStr = pTemp; _GetRefer() = 1;//新開的空間 } return _pStr[index];}const char& String::operator[](size_t index)const{ return _pStr[index];}新聞熱點
疑難解答