之前的博客已經(jīng)給出了如何自己定義一個(gè)string類(lèi),以及其內(nèi)部應(yīng)該有的操作,今天就讓我們根據(jù)STL庫(kù)中給出的string來(lái)看看,它重要的寫(xiě)實(shí)拷貝實(shí)現(xiàn)和一點(diǎn)點(diǎn)讀時(shí)拷貝(也是寫(xiě)時(shí)拷貝)
創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:做網(wǎng)站、成都網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿(mǎn)足客戶(hù)于互聯(lián)網(wǎng)時(shí)代的社旗網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
1、寫(xiě)時(shí)拷貝(copy-on-write)
class String { public: String(const String &str) :_pData(NULL) { String temp(str._pData); swap(_pData,temp._pData); } private: char *_pData; } void test() { String s1("hello world");//構(gòu)造 String s2(s1);//拷貝構(gòu)造 }
這里面實(shí)現(xiàn)的是用空間換時(shí)間的一種方法,定義極其簡(jiǎn)單,然而大神們寫(xiě)出來(lái)的STL庫(kù)中的string是更為精巧的。就是應(yīng)用了寫(xiě)實(shí)拷貝的技術(shù),防止淺拷貝發(fā)生,并且還省了空間,
那么問(wèn)題來(lái)了????
Q:什么是寫(xiě)時(shí)拷貝呢?
A:寫(xiě)時(shí)拷貝就是一種拖延戰(zhàn)術(shù),當(dāng)你真正用到的時(shí)候才去給它開(kāi)辟空間,不然它只是看起來(lái)存在,實(shí)際上只是邏輯上的存在,這種方法在STL的string中體現(xiàn)的很明顯。
由于string類(lèi)是用char* 實(shí)現(xiàn)的,其內(nèi)存都是在堆上開(kāi)辟和釋放的。堆上的空間利用要很小心,所以當(dāng)你定義一個(gè)sring類(lèi)的對(duì)象,并且想對(duì)這個(gè)對(duì)象求地址,其返回值是const char*類(lèi)型,onlyread屬性哦,如果還想對(duì)該地址的內(nèi)容做什么改變,只能通過(guò)string給的方法去修改。
舉個(gè)栗子:
#include#include using namespace std; int main() { string s1("再來(lái)一遍:hello world"); string s2(s1); //c++方式打印一個(gè)字符串的地址!?。。?! //static_cast---c++中的強(qiáng)制類(lèi)型轉(zhuǎn)換,不檢查 //string的c_str()方法返回值是const char * cout< (s1.c_str())< (s2.c_str())< _<)~~~~我也這么覺(jué)得 printf("%x\n",s1.c_str()); printf("%x\n",s2.c_str()); }
(vs2010版本)
結(jié)果是不是和你想的不一樣。。。。(明明應(yīng)該不變的說(shuō)~)
vc 6.0版本下:s1,s2的地址是一樣的。這里就不進(jìn)行截屏了,如果有興趣的同學(xué),下去可以試試哈~
那么當(dāng)對(duì)s1,s2進(jìn)行修改時(shí)是怎么樣的呢
s1[0]='h'; s2[0]='w';
(vs2010版本)
VC6.0版本下:s1,s2的地址不一樣(同vs2010版本)
所以我們得出的結(jié)論是:
當(dāng)對(duì)string對(duì)象只進(jìn)行拷貝構(gòu)造時(shí),發(fā)生的是寫(xiě)時(shí)拷貝(假拷貝),只有對(duì)其對(duì)象進(jìn)行修改時(shí)(有寫(xiě)的操作),才對(duì)其對(duì)象另外開(kāi)辟空間,進(jìn)行修改。
要想達(dá)到這樣的效果,在一定程度上節(jié)省了空間。
必須做到兩點(diǎn):內(nèi)存的共享,寫(xiě)時(shí)拷貝。
(1)copy-on-write的原理?
“引用計(jì)數(shù)”,程序猿就是這般機(jī)智~~~~
當(dāng)對(duì)象s1實(shí)例化,調(diào)用構(gòu)造,引用計(jì)數(shù)初始化=1;
當(dāng)有對(duì)象對(duì)s1進(jìn)行拷貝時(shí),s1的引用計(jì)數(shù)+1;
當(dāng)有對(duì)象是由s1拷貝來(lái)的或者是s1自身進(jìn)行析構(gòu)是,s1的引用計(jì)數(shù)進(jìn)行-1;
當(dāng)有對(duì)象是由s1拷貝來(lái)的或者是s1自身需要修改時(shí),進(jìn)行真拷貝,并且引用計(jì)數(shù)-1;
當(dāng)引用計(jì)數(shù)==0的時(shí)候,進(jìn)行真正的析構(gòu)。
(2)引用計(jì)數(shù)應(yīng)該如何設(shè)計(jì)在?
關(guān)于引用計(jì)數(shù)的實(shí)現(xiàn),你是不是也有這樣的疑惑呢?
當(dāng)類(lèi)的對(duì)象之間進(jìn)行共享時(shí),引用計(jì)數(shù)也是共享的
當(dāng)類(lèi)中的對(duì)象從公共中脫離出來(lái),引用計(jì)數(shù)就是它自己的了。
那么如何做到從獨(dú)立--->共享--->獨(dú)立的呢???
如果你想將引用計(jì)數(shù)當(dāng)做String類(lèi)的成員變量,那么什么樣的類(lèi)型適合它呢?
int _count; 那么每個(gè)對(duì)象的實(shí)例化都擁有一個(gè)自己的引用計(jì)數(shù),無(wú)法實(shí)現(xiàn)共享
class String { public: String(pData=NULL) :_pData(new char[strlen(pData)+1]) ,_count(1) { strcpy(_pData,pData); } ~String() { if(--_count==0) { delete []_pData; } } String(String &str) :_pData(str._pData) { str._count++; _count=str._count; } private: char *_pData; int _count; }; string s1="hello world"; string s2(s1); //s1構(gòu)造,s2拷貝構(gòu)造:s1和s2指向同一空間,s1和s2的_count都變成2 //當(dāng)s2先析構(gòu),s2的_count--變成1,不釋放 //當(dāng)s1析構(gòu)時(shí),s1的_count--變成1,不釋放 //造成內(nèi)存泄露
static int _pCount;那么每個(gè)對(duì)象的實(shí)例化都擁有這唯一的一個(gè)引用計(jì)數(shù),共享范圍過(guò)大
class String { public: String(pData=NULL) :_pData(new char[strlen(pData)+1]) { _count=1; strcpy(_pData,pData); } ~String() { if(--_count==0) { delete []_pData; } } String(String &str) //不加const,不然底下的淺拷貝會(huì)出錯(cuò) :_pData(str._pData) { str._count++; } private: char *_pData; static int _count; //靜態(tài)的成員變量要在類(lèi)外進(jìn)行初始化 }; int String::_count=0; string s1="hello world"; string s2(s1); string s3("error"); //s1構(gòu)造,s2拷貝構(gòu)造:s1和s2指向同一空間,_count都變成2 //s3構(gòu)造,_count變成1 //當(dāng)s3先析構(gòu),_count--變成0,釋放 //s1,s2造成內(nèi)存泄露
int *_pCount;可以實(shí)現(xiàn)引用計(jì)數(shù)。
class String { public: String(pData=NULL) :_pData(new char[strlen(pData)+1]) ,_pCount(new int(1)) { strcpy(_pData,pData); } ~String() { if(--(*_pCount)==0) { delete []_pData; delete _pCount; } } String& operator=(const String *str) { if(_pData!=str._pData) { if(--(*_pCount)==0) { delete _pCount; delete []_pData; } (*str._pCount)++; _pCount=str._pCount; _pData=str._pData; } return *this; } String(String &str) //不加const,不然底下的淺拷貝會(huì)出錯(cuò) :_pData(str._pData) ,_pCount(str._pCount) { (*str._pCount)++; } private: char *_pData; int *_pCount; };
這些字符串都是在堆上開(kāi)辟的,那么引用計(jì)數(shù)也可以在堆上開(kāi)辟,要從邏輯上,看引用計(jì)數(shù)是個(gè)指針,存次數(shù),從物理上看,引用計(jì)數(shù)應(yīng)該和字符指針?lè)旁谝黄穑阌诠芾?。讓?shù)據(jù)相同的對(duì)象都可以共享同一片內(nèi)存。
綜上,引用計(jì)數(shù)的設(shè)計(jì)如圖:
(3)引用計(jì)數(shù)什么時(shí)候需要共享呢?
情況1:string s2(s1); //s2拷貝自s1,即s2中的數(shù)據(jù)和s1的一樣
情況2:string s2; s2=s1;//s2的數(shù)據(jù)由s1賦值而來(lái),即s2中的數(shù)據(jù)和s1的一樣
綜上所述:
string類(lèi)中的拷貝構(gòu)造和賦值運(yùn)算符重載需要引用計(jì)數(shù)
(4)什么情況下需要進(jìn)行寫(xiě)時(shí)拷貝
對(duì)內(nèi)容有修改時(shí)
(5)c++版實(shí)現(xiàn)代碼
class String { private: char *_pData; //引用計(jì)數(shù)存在于_pData[-1] public: //構(gòu)造函數(shù) String(pData=NULL) :_pData(new char[strlen(pData)+1+sizeof(int)]) { //強(qiáng)轉(zhuǎn)在頭上4個(gè)字節(jié)存放引用計(jì)數(shù)的值 (*(int *)_pData)=1; //恢復(fù)其字符串的長(zhǎng)度 _pData+=4; strcpy(_pData,pData); } //拷貝構(gòu)造 String(const String&str) :_pData(str. _pData) { //(*(--(int *)str._pData)) ++; //這個(gè)版本是錯(cuò)的,大家看看錯(cuò)在哪里?可以留言告訴我哦 (*(--(int*)_pData-1)++; } //賦值運(yùn)算符重載 String& operator=(const String &str) { if(_pData!=str._pData) { if(--(*(--(int*)_pData-1)==0) { _pData-=4; delete []_pData; } else { _pData=str._pData; (*(--(int*)_pData-1)++; } } return *this; } //析構(gòu)函數(shù) ~String() { if(--(*(--(int*)_pData-1)==0) { _pData-=4; delete []_pData; } } };
2、讀時(shí)拷貝(copy-on-read)
當(dāng)C++的STL庫(kù)中的string被這么利用時(shí):
string s1="hello world"; long begin = getcurrenttick(); for(size_t i=0;i
網(wǎng)站名稱(chēng):通過(guò)STL中的string看寫(xiě)時(shí)拷貝和讀時(shí)拷貝
URL鏈接:http://weahome.cn/article/jpgois.html