什么情況下會用到c++中的拷貝構造函數】:
?1)用已經存在的同類的對象去構造出另一個新的對象
? 2)當函數的形參是類的對象時,這時調用此函數,使用的是值的拷貝,也會調用拷貝構造函數
? 3)當函數的返回值是類的對象時,這時當函數調用完后,會將函數的對象拷貝構造出一個臨時的對象并傳給函數的返回處
【淺拷貝】:(位拷貝(值拷貝))
1、概念:所謂的淺拷貝就是當在進行對象的復制時,只是進行對象的數據成員的拷貝,其中默認的拷貝構造函數也是淺拷貝。大多數情況下,使用淺拷貝是沒有問題的,但是當出現動態成員,就會出現問題。
2、關于淺拷貝的使用舉例:
- #include<iostream>??
- using?namespace?std;??
- class?Test??
- {??
- public:??
- ????//構造函數??
- ????Test(int?a)??
- ????????:_a(a)??
- ????{}??
- ????//拷貝構造函數??
- ????Test(const?Test&?x)??
- ????{??
- ????????_a?=?x._a;??
- ????}??
- ??
- private:??
- ????int?_a;??
- };??
- int?main()??
- {??
- ????Test?b(10);??
- ????Test?c(b);??
- ????return?0;??
- }??
3、淺拷貝的缺陷:
? ? ? 淺拷貝對于指針成員不可行。多個對象共用同一塊空間,同一內存地址,但是在調用析構函數釋放空間的時候,多次調用析構函數,這塊空間被釋放了多次,此時程序就會崩潰。
【引用計數的拷貝】:
1、(怎么引入的)概念:因為淺拷貝的缺陷,所以在這個時候我們就引入了引用計數的拷貝。
? ? ?【說明】:引用計數的拷貝是用來解決淺拷貝存在的問題的,所以它也是一種淺拷貝
2、如何實現:我們為每個內存的字符數組添加一個引用計數pcount,即就是在構造函數申請空間的時候多申請出來4個字節。表示有多少個對象使用這塊內存,有多少個對象使用,就讓pcount值加1,當對象被析構的時候,讓pcount值減1,當pcount值為0的時候,將這塊內存釋放掉。當然pcount也要實現內存共享,所以它也是一個堆中的數據,每個對象都有一個指向它的指針。
3、【說明】:但在此時,pcount的類型的選取,就會要有所考慮?
? ? 1)如果選取int類型:(不采取)
- #include<iostream>??
- using?namespace?std;??
- class?String??
- {??
- public:??
- ??????
- ????String(const?char*?ptr?=?"")??
- ????{??
- ????????if(ptr?==?NULL)??
- ????????{??
- ????????????_ptr?=?new?char[1];??
- ????????????_pcount?=?1;??
- ????????????*_ptr?=?'\0';??
- ????????}??
- ????????else??
- ????????{??
- ????????????_pcount?=?1;??
- ????????????_ptr?=?new?char[strlen(ptr)+1];??
- ????????????strcpy(_ptr,ptr);??
- ????????}??
- ????}??
- ??????
- ????String(String&?s)??
- ????????:_ptr(s._ptr)??
- ????????,_pcount(s._pcount)??
- ????{??
- ????????_pcount++;??
- ????}??
- ??????
- ????String&?operator=(const?String&?s)??
- ????{??
- ????????if(this?!=?&s)??
- ????????{??
- ????????????if(--_pcount?==?0)??
- ????????????{??
- ????????????????delete[]?_ptr;??
- ??????????????????
- ????????????}??
- ????????????else??
- ????????????{??
- ????????????????_ptr?=?s._ptr;??
- ????????????????_pcount?=?s._pcount;??
- ????????????????(_pcount)++;??
- ????????????}??
- ????????}??
- ????????return?*this;??
- ????}??
- ??????
- ????~String()??
- ????{??
- ????????if((0?==?--_pcount)?&&?_ptr!=?NULL)??
- ????????{??
- ????????????delete[]_ptr;??
- ??????????????
- ????????????_ptr?=?NULL;??
- ????????}??
- ??????????
- ????}??
- ??????
- ????char&?operator[](size_t?size)??
- ????{??
- ????????if(--_pcount?>1)??
- ????????{??
- ????????????char*?ptemp?=?new?char[strlen(_ptr)+1];??
- ????????????int?pcount?=?1;??
- ????????????strcpy(ptemp,_ptr);??
- ????????????_pcount--;??
- ????????????_ptr?=?ptemp;??
- ????????????_pcount?=?pcount;???
- ????????}??
- ????????return?_ptr[size];??
- ????}??
- private:??
- ????char*_ptr;??
- ????int?_pcount;??
- };??
- ??
- void?FunTest()??
- {??
- ????String?s1("hello");??
- ????String?s2(s1);??
- ????String?s3(s2);??
- ????s3?=?s2;??
- }??
- int?main()??
- {??
- ????FunTest();??
- ????return?0;??
- }??
調試:

(注意這里我將斷點就走到s2,意在說明問題):本來增加s2的時候,兩個對象的計數應該是一樣的,但是現在一個是1,一個是2,不同步,我們了解到這兩個對象的計數變量的地址是不一樣的。說明此pcount是公共的,可以被多個對象同時訪問。
? ? 2)如果選取的是static類型的:(不采取) ?
- #include<iostream>??
- using?namespace?std;??
- class?String??
- {??
- public:??
- ??????
- ????String(const?char*?ptr?=?"")??
- ????{??
- ????????if(ptr?==?NULL)??
- ????????{??
- ????????????_ptr?=?new?char[1];??
- ????????????_pcount?=?1;??
- ????????????*_ptr?=?'\0';??
- ????????}??
- ????????else??
- ????????{??
- ????????????_pcount?=?1;??
- ????????????_ptr?=?new?char[strlen(ptr)+1];??
- ????????????strcpy(_ptr,ptr);??
- ????????}??
- ????}??
- ??????
- ????String(String&?s)??
- ????????:_ptr(s._ptr)??
- ????{??
- ????????_pcount++;????
- ????}??
- ??????
- ????String&?operator=(const?String&?s)??
- ????{??
- ????????if(this?!=?&s)??
- ????????{??
- ????????????if(--_pcount?==?0)??
- ????????????{??
- ????????????????delete[]?_ptr;??
- ??????????????????
- ????????????}??
- ????????????else??
- ????????????{??
- ????????????????_ptr?=?s._ptr;??
- ????????????????_pcount?=?s._pcount;??
- ????????????????(_pcount)++;??
- ????????????}??
- ????????}??
- ????????return?*this;??
- ????}??
- ??????
- ????~String()??
- ????{??
- ????????if((0?==?--_pcount)?&&?_ptr!=?NULL)??
- ????????{??
- ????????????delete[]_ptr;??
- ??????????????
- ????????????_ptr?=?NULL;??
- ????????}??
- ??????????
- ????}??
- ??????
- ????char&?operator[](size_t?size)??
- ????{??
- ????????if(--_pcount?>1)??
- ????????{??
- ????????????char*?ptemp?=?new?char[strlen(_ptr)+1];??
- ????????????int?pcount?=?1;??
- ????????????strcpy(ptemp,_ptr);??
- ????????????_pcount--;??
- ????????????_ptr?=?ptemp;??
- ????????????_pcount?=?pcount;???
- ????????}??
- ????????return?_ptr[size];??
- ????}??
- private:??
- ????char*_ptr;??
- ????static?int?_pcount;??
- };??
- int?String::_pcount?=?0;??
- void?FunTest()??
- {??
- ????String?s1("hello");??
- ????String?s2(s1);??
- ????String?s3(s2);??
- ????s3?=?s2;??
- ????String?s4("world");??
- ????String?s5(s4);??
- }??
- int?main()??
- {??
- ????FunTest();??
- ????return?0;??
- }??
調試:
? 先走到s3,然后走到s4,用s4來構造s5,結果就不對了,走到s4的時候,計數器又變成了1,說明這5個對象公用一個pcount,不能實現引用計數
? ? 3)那么我們這樣想:如果一個對象第一次開辟空間存放字符串再開辟一塊新的空間存放新的額引用計數,當它拷貝構造其它對象時讓其它對象的引用計數都指向存放引用計數的同一塊空間,pcount設置成int*就可以啦,但是這種方式有缺陷。(讀者可以自己實現下)
? ? ? ?缺陷一:每次new兩塊空間,創建多個對象的時候效率比較低
? ? ? ?缺陷二:它多次分配小塊空間,容易造成內存碎片化,導致分配不出來大塊內存
4、代碼實現:(所以我將優化版的代碼貼出來,其實就是仿照new的底層實現,開辟一塊空間,但是它的頭幾個字節用于計數)
- <span?style="font-size:18px;">#include<iostream>??
- using?namespace?std;??
- class?String??
- {??
- public:??
- ????String(char?*ptr?=?"")??
- ????{??
- ????????if(ptr?==?NULL)??
- ????????{??
- ????????????_ptr?=?new?char[strlen(ptr)+5];??
- ????????????_ptr?=?new?char[5];??
- ????????????*(_ptr+4)?=?'\0';??
- ????????}??
- ????????else??
- ????????{??
- ????????????_ptr?=?new?char[strlen(ptr)+5];??
- ????????????*((int*)_ptr)?=?1;??
- ????????????_ptr?+=?4;??
- ????????????strcpy(_ptr,ptr);??
- ????????}??
- ????}??
- ????String(const?String&?s)??
- ????????:_ptr(s._ptr)??
- ????{??
- ????????(*((int*)(_ptr-4)))++;??
- ????}??
- ????String&?operator=(const?String&?s)??
- ????{??
- ????????if(this?!=?&s)??
- ????????{??
- ????????????if(--(*((int*)(_ptr-4)))?==?0)??
- ????????????{??
- ????????????????delete[]_ptr;??
- ????????????}??
- ????????????else??
- ????????????{??
- ????????????????_ptr?=?s._ptr;??
- ????????????????++(*(int*)(_ptr-4));??
- ????????????}??
- ????????}??
- ????????return?*this;??
- ????}??
- ????~String()??
- ????{??
- ????????if((_ptr?!=?NULL)?&&?((--(*((int*)(_ptr-4))))?==?0))??
- ????????{??
- ????????????delete[]_ptr;??
- ????????????_ptr?=?NULL;??
- ????????}??
- ????}??
- private:??
- ????char*?_ptr;??
- };??
- void?Funtest()??
- {??
- ????String?s1("hello");??
- ????String?s2(s1);??
- ????String?s3(s2);???
- ????s3?=?s2;??
- }??
- int?main()??
- {??
- ????Funtest();??
- ????return?0;??
- }</span>??
【深拷貝】:(址拷貝)
1、概念:采取在堆中申請新的空間來存取數據,這樣數據之間相互獨立。址拷貝。
2、舉例:(string類中的拷貝構造函數)
- <span?style="font-size:18px;">String(const?String&?s)??
- ????{??
- ????????_ptr?=?new?char[strlen(s._ptr)+1];??
- ????????strcpy(_ptr,s._ptr);??
- ????}</span>??
【寫時拷貝】:
1、(如何引入的)概念:但是當其中一個對象改變它的值時,其他對象的值就會隨之改變,所以此時我們采取這樣一種做法,就是寫時拷貝。
2、核心思想:
(寫入時拷貝)如果有多個調用者同時要求相同資源(如內存或磁盤上的數據存儲),它們會共同獲取相同的指針指向的資源,直到某個調用者試圖修改資源的內容時,系統才會真正復制一份專用副本給該調用者,而其他調用者所見到的最初的資源仍然保持不變。這過程中對其他調用者都是透明的。做法的優點:如果調用者沒有修改該資源,就不會有副本被創建,因此多個調用者只是讀取操作時可以共享同一份資源
(寫時拷貝)指用淺拷貝的方法拷貝其他對象,多個指針指向同一塊空間,只有當對其中一個對象修改時,才會開辟一個新的空間給這個對象,和它原來指向同一空間的對象不會收到影響。
3、做法:給要改變值的那個對象重新new出一塊內存,然后先把之前的引用的字符數據復制到新的字符數組中,這就是寫時拷貝。注意,同時還要把之前指向的內存的引用計數減1(因為它指向了新的堆中的字符數組),并在堆中重新new一個塊內存,用于保存新的引用計數,同時把新的字符數組的引用計數置為1。因為此時只有一個對象(就是改變值的對象)在使用這個內存。
4、代碼實現:
- <span?style="font-size:18px;">#include<iostream>??
- using?namespace?std;??
- class?String??
- {??
- public:??
- ??????
- ????String(const?char*?ptr?=?"")??
- ????{??
- ????????if(ptr?==?NULL)??
- ????????{??
- ????????????_ptr?=?new?char[1];??
- ????????????_pcount?=?new?int(1);??
- ????????????*_ptr?=?'\0';??
- ????????}??
- ????????else??
- ????????{??
- ????????????_pcount?=?new?int(1);??
- ????????????_ptr?=?new?char[strlen(ptr)+1];??
- ????????????strcpy(_ptr,ptr);??
- ????????}??
- ????}??
- ??????
- ????String(String&?s)??
- ????????:_ptr(s._ptr)??
- ????????,_pcount(s._pcount)??
- ????{??
- ????????(*_pcount)++;??
- ????}??
- ??????
- ????String&?operator=(const?String&?s)??
- ????{??
- ????????if(this?!=?&s)??
- ????????{??
- ????????????if(--(*_pcount)?==?0)??
- ????????????{??
- ????????????????delete[]?_ptr;??
- ????????????????delete?_pcount;??
- ????????????}??
- ????????????else??
- ????????????{??
- ????????????????_ptr?=?s._ptr;??
- ????????????????_pcount?=?s._pcount;??
- ????????????????(*_pcount)++;??
- ????????????}??
- ????????}??
- ????????return?*this;??
- ????}??
- ??????
- ????~String()??
- ????{??
- ????????if(0?==?--(*_pcount)?&&?_ptr!=?NULL)??
- ????????{??
- ????????????delete[]_ptr;??
- ????????????delete?_pcount;??
- ????????????_ptr?=?NULL;??
- ????????}??
- ??????????
- ????}??
- ??????
- ????char&?operator[](size_t?size)??
- ????{??
- ????????if(--(*_pcount)?>1)??
- ????????{??
- ????????????char*?ptemp?=?new?char[strlen(_ptr)+1];??
- ????????????int*?pcount?=?new?int(1);??
- ????????????strcpy(ptemp,_ptr);??
- ????????????(*_pcount)--;??
- ????????????_ptr?=?ptemp;??
- ????????????_pcount?=?pcount;???
- ????????}??
- ????????return?_ptr[size];??
- ????}??
- private:??
- ????char*_ptr;??
- ????int*?_pcount;??
- };??
- ??
- void?FunTest()??
- {??
- ????String?s1("hello");??
- ????String?s2(s1);??
- ????String?s3(s2);??
- ????s3?=?s2;??
- ????String?s4(s1);??
- ????s1[0]?=?'y';??
- }??
- int?main()??
- {??
- ????FunTest();??
- ????return?0;??
- }</span>??
經調試,結果如下:

【注意】:因為不止一個對象使用這一塊內存,當修改自己的時,也等于修改了他人的。在向這塊存儲單元寫之前,應該確信沒有其他人使用它。如果引用計數大于1,在寫之前必須拷貝這塊存儲單元,這樣就不會影響他人了。