第3章 拷貝控制_第1頁
第3章 拷貝控制_第2頁
第3章 拷貝控制_第3頁
第3章 拷貝控制_第4頁
第3章 拷貝控制_第5頁
已閱讀5頁,還剩50頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第3章拷貝控制本章主要內(nèi)容:對(duì)象傳遞、復(fù)制和賦值 具有動(dòng)態(tài)分配的類

拷貝構(gòu)造 拷貝賦值

C++11移動(dòng)構(gòu)造

C++11移動(dòng)賦值 std::move應(yīng)用 典型范例——鏈表表示的集合類實(shí)現(xiàn)

*鏈集合向量空間擴(kuò)充探討

計(jì)算機(jī)學(xué)院李衛(wèi)明對(duì)象的傳遞涉及對(duì)象的復(fù)制、賦值或轉(zhuǎn)移??截惪刂坪瘮?shù)由類的拷貝構(gòu)造、移動(dòng)構(gòu)造、拷貝賦值、移動(dòng)賦值和析構(gòu)五個(gè)函數(shù)組成。前面我們設(shè)計(jì)和實(shí)現(xiàn)的類,數(shù)據(jù)成員類型都是內(nèi)置數(shù)據(jù)類型、固定大小數(shù)組、或類類型,這樣的類類型一般無需定義上述五個(gè)拷貝控制函數(shù),不影響這些類型對(duì)象的復(fù)制、賦值、轉(zhuǎn)移,也不影響它們的正確析構(gòu)。像集合、棧、字符串、向量等具有比較復(fù)雜的狀態(tài)的對(duì)象,往往需要采用動(dòng)態(tài)分配,如用鏈表存儲(chǔ)集合內(nèi)的元素、用動(dòng)態(tài)分配的連續(xù)空間存放向量元素等,這些類需要定義和實(shí)現(xiàn)五個(gè)拷貝控制函數(shù)。

標(biāo)準(zhǔn)庫提供的vector、string、list等類模板就是在動(dòng)態(tài)分配基礎(chǔ)上實(shí)現(xiàn)的。計(jì)算機(jī)學(xué)院李衛(wèi)明3.1 對(duì)象傳遞、復(fù)制和賦值C++函數(shù)參數(shù)可以傳遞對(duì)象的引用、對(duì)象的地址、對(duì)象數(shù)組,也可以傳遞對(duì)象復(fù)制的副本。C++函數(shù)參數(shù)傳遞對(duì)象的引用:實(shí)參是對(duì)象名,形參本質(zhì)上是實(shí)參的別名,實(shí)參和形參是同一個(gè)對(duì)象。對(duì)于大型對(duì)象,引用傳遞具有極高傳遞效率,是C++函數(shù)間最為普遍的參數(shù)傳遞方式,如果希望函數(shù)處理期間對(duì)象不發(fā)生變化,一般聲明常引用。

假設(shè)CSet是一個(gè)類,常引用聲明和調(diào)用形式如下: CSetUnion(constCSet&rhs); C=A.Union(B);計(jì)算機(jī)學(xué)院李衛(wèi)明C++函數(shù)參數(shù)傳遞對(duì)象的地址:實(shí)參是對(duì)象的地址,形參是類指針類型,形參指向?qū)崊?duì)象。對(duì)于大型對(duì)象,一樣具有極高傳遞效率,但不如引用傳遞方式直接明了,較少采用,如果希望函數(shù)處理期間對(duì)象不發(fā)生變化,可以聲明指針?biāo)笇?duì)象不可變。傳遞對(duì)象地址時(shí),函數(shù)聲明和調(diào)用形式如下: CSetUnion(constCSet*pSet); C=A.Union(&B);計(jì)算機(jī)學(xué)院李衛(wèi)明C++函數(shù)參數(shù)傳遞對(duì)象數(shù)組:實(shí)參是對(duì)象數(shù)組名,形參是元素具有類類型的數(shù)組,形參實(shí)際復(fù)制了代表數(shù)組起始地址的實(shí)參值。對(duì)于大型對(duì)象數(shù)組,具有極高傳遞效率,一般傳遞對(duì)象數(shù)組需求較少。傳遞對(duì)象數(shù)組時(shí),函數(shù)聲明和調(diào)用形式如下:doubleTotalSize(CCircleallCircles[],intiCount);x=TotalSize(circles,n);//circles是CCircle對(duì)象組成的數(shù)組計(jì)算機(jī)學(xué)院李衛(wèi)明C++函數(shù)參數(shù)采用傳值形式:實(shí)參是對(duì)象名,形參是根據(jù)實(shí)參對(duì)象在運(yùn)行棧上新復(fù)制構(gòu)造的對(duì)象,構(gòu)造完成后,形參和實(shí)參是2個(gè)獨(dú)立的對(duì)象,形參變化,實(shí)參不變。對(duì)于大型對(duì)象,復(fù)制效率較低,一般在確實(shí)必要時(shí)才采用。函數(shù)聲明和調(diào)用形式如下: voidDoSomeThing(CSampleobj); DoSomeThing(obj);計(jì)算機(jī)學(xué)院李衛(wèi)明與參數(shù)傳遞類似,關(guān)于函數(shù)返回值類型,也有返回對(duì)象引用、對(duì)象指針和對(duì)象值類型三種方式。對(duì)于局部對(duì)象,由于函數(shù)執(zhí)行完畢后局部對(duì)象會(huì)析構(gòu),因此,不可返回局部對(duì)象的引用或指針,否則,根據(jù)函數(shù)返回結(jié)果去訪問對(duì)象會(huì)導(dǎo)致不確定的錯(cuò)誤結(jié)果。當(dāng)函數(shù)返回對(duì)象值類型時(shí),編譯器會(huì)通過復(fù)制構(gòu)造一個(gè)臨時(shí)副本對(duì)象返回,因此,函數(shù)返回值具有對(duì)象類型時(shí),函數(shù)可以返回局部對(duì)象。計(jì)算機(jī)學(xué)院李衛(wèi)明C++程序設(shè)計(jì)中,還經(jīng)常需要顯式根據(jù)對(duì)象A復(fù)制構(gòu)造一個(gè)新對(duì)象B,如:CSampleB(A);或CSampleB{A};或CSampleB=A;除上述參數(shù)傳值、函數(shù)返回對(duì)象和顯式復(fù)制構(gòu)造外,如果對(duì)象作為另一個(gè)類型大對(duì)象的子對(duì)象,隨著大對(duì)象的復(fù)制構(gòu)造,子對(duì)象也會(huì)復(fù)制構(gòu)造。對(duì)象的復(fù)制是通過復(fù)制構(gòu)造函數(shù)完成的,復(fù)制構(gòu)造函數(shù)也稱為拷貝構(gòu)造函數(shù)。

通常,C++編譯器會(huì)自動(dòng)合成復(fù)制構(gòu)造函數(shù),合成的復(fù)制構(gòu)造函數(shù)實(shí)際效果是逐個(gè)數(shù)據(jù)成員的復(fù)制。如果一個(gè)類的所有數(shù)據(jù)成員類型都是內(nèi)置數(shù)據(jù)類型、固定大小數(shù)組,或類類型,編譯器合成的復(fù)制構(gòu)造函數(shù)就是我們需要的效果,如同前面簡單集合類所示,對(duì)于這樣的類,我們無需特殊處理就可以使用復(fù)制構(gòu)造。計(jì)算機(jī)學(xué)院李衛(wèi)明類似情況,C++程序中經(jīng)常需要給對(duì)象賦值,如:CSampleA,B;...B=A;上述賦值語句將對(duì)象A賦值給對(duì)象B。執(zhí)行賦值前已存在2個(gè)獨(dú)立對(duì)象A、B,賦值完成后2個(gè)對(duì)象狀態(tài)相同:對(duì)象B變成對(duì)象A一樣的狀態(tài)。注意,這與前述對(duì)象A復(fù)制構(gòu)造對(duì)象B不同,復(fù)制構(gòu)造前只有一個(gè)對(duì)象A存在,復(fù)制構(gòu)造完成后有2個(gè)獨(dú)立且狀態(tài)一樣的對(duì)象。對(duì)象的賦值是通過賦值運(yùn)算符完成的。通常,C++編譯器會(huì)自動(dòng)合成賦值運(yùn)算符,合成的賦值運(yùn)算符實(shí)際效果是逐個(gè)數(shù)據(jù)成員的賦值。如果一個(gè)類的所有數(shù)據(jù)成員類型都是內(nèi)置數(shù)據(jù)類型、固定大小數(shù)組,或類類型,編譯器合成的賦值運(yùn)算符就是我們需要的效果,如同前面簡單集合類所示,對(duì)于這樣的類,我們無需特殊處理就可以使用賦值。計(jì)算機(jī)學(xué)院李衛(wèi)明//使用缺省復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符的示例。classTime{private:

inthour; //時(shí)

intminute; //分

intsecond; //秒public:

Time(inth=0,intm=0,ints=0) :hour(h),minute(m),second(s){} //構(gòu)造函數(shù)

voidSet(inth,intm,ints) //設(shè)置時(shí)間

{hour=h;minute=m;second=s;} voidShow()const //顯示時(shí)間

{cout<<hour<<":"<<minute<<":"<<second<<endl;}};intmain() //主函數(shù)main(){ Timet1(6,16,18),t2; //構(gòu)造函數(shù)的參數(shù)都采用默認(rèn)值

t1.Show(); //顯示時(shí)間6:16:18 t2=t1; //利用合成賦值函數(shù)賦值 t2.Show(); //顯示時(shí)間6:16:18 Timet3(t1); //利用合成拷貝構(gòu)造函數(shù)構(gòu)造對(duì)象t3 t3.Show(); //顯示時(shí)間6:16:18}計(jì)算機(jī)學(xué)院李衛(wèi)明3.2 具有動(dòng)態(tài)分配的類C++程序中,并非所有類的數(shù)據(jù)成員類型都是內(nèi)置數(shù)據(jù)類型、固定大小數(shù)組,還經(jīng)常遇到一些如集合、棧、字符串、向量等對(duì)象,具有比較復(fù)雜的狀態(tài),為表示這些狀態(tài)復(fù)雜多變的對(duì)象,往往需要采用動(dòng)態(tài)分配,如用鏈表表示集合內(nèi)的元素、用動(dòng)態(tài)分配的連續(xù)空間存放向量內(nèi)容等等,如標(biāo)準(zhǔn)庫的vector、string、set等類模板就是在動(dòng)態(tài)分配基礎(chǔ)上實(shí)現(xiàn)的。對(duì)于具有動(dòng)態(tài)分配的類,使用編譯器合成的拷貝構(gòu)造函數(shù)和復(fù)制賦值運(yùn)算符,運(yùn)行時(shí)會(huì)導(dǎo)致嚴(yán)重問題,但編譯器并不會(huì)發(fā)出警告。計(jì)算機(jī)學(xué)院李衛(wèi)明下面以鏈集合類和簡單字符串類為例,分析需要?jiǎng)討B(tài)分配的類如何設(shè)計(jì)和實(shí)現(xiàn)拷貝控制函數(shù)。

鏈集合類用帶頭結(jié)點(diǎn)的單鏈表存儲(chǔ)集合內(nèi)元素,不限定集合元素個(gè)數(shù)。圖3.1鏈集合對(duì)象內(nèi)存狀態(tài)示意圖計(jì)算機(jī)學(xué)院李衛(wèi)明

圖3.2鏈集合類對(duì)象的拷貝構(gòu)造前狀態(tài)示意圖圖3.3編譯器合成的鏈集合類拷貝構(gòu)造效果圖計(jì)算機(jī)學(xué)院李衛(wèi)明3.2.1 拷貝構(gòu)造使用默認(rèn)復(fù)制構(gòu)造函數(shù)可能出現(xiàn)運(yùn)行時(shí)錯(cuò)誤默認(rèn)復(fù)制構(gòu)造函數(shù)只簡單地將源對(duì)象的數(shù)據(jù)成員的值復(fù)制給目的對(duì)象的相應(yīng)數(shù)據(jù)成員當(dāng)類具有指針成員時(shí),缺省拷貝構(gòu)造函數(shù)為淺層復(fù)制。當(dāng)一個(gè)類中包含指針類型的數(shù)據(jù)成員,并且通過指針在構(gòu)造函數(shù)中動(dòng)態(tài)申請了存儲(chǔ)空間,在析構(gòu)函數(shù)中通過指針釋放了動(dòng)態(tài)存儲(chǔ)空間,這種情況下默認(rèn)復(fù)制構(gòu)造函數(shù)將會(huì)出現(xiàn)運(yùn)行時(shí)錯(cuò)誤。A對(duì)象指針成員B對(duì)象淺層復(fù)制計(jì)算機(jī)學(xué)院李衛(wèi)明//使用默認(rèn)復(fù)制構(gòu)造函數(shù)出現(xiàn)運(yùn)行時(shí)錯(cuò)誤的示例。classString{private:

char*strValue; //串值public:

String(char*s="") //構(gòu)造函數(shù)

{ if(s==NULL)s=""; //將空指針轉(zhuǎn)化為空串

strValue=newchar[strlen(s)+1]; //分配存儲(chǔ)空間

strcpy(strValue,s); //復(fù)制串值

} ~String(){delete[]strValue;} //析構(gòu)函數(shù)

voidShow(){cout<<strValue<<endl;} //顯示串

};intmain() //主函數(shù)main(){ Strings1(“test"); //調(diào)用普通構(gòu)造函數(shù)的生成對(duì)象s1 Strings2(s1); //調(diào)用默認(rèn)復(fù)制構(gòu)造函數(shù)的生成對(duì)象s2 s1.Show(); //顯示串s1 s2.Show(); //顯示串s2}程序運(yùn)行時(shí)可能的屏幕輸出如下:testtest請按任意鍵繼續(xù)...當(dāng)用戶按任一鍵時(shí),屏幕將會(huì)顯示類似DebugAssertionFailed!的錯(cuò)誤計(jì)算機(jī)學(xué)院李衛(wèi)明在執(zhí)行“Strings1(“test”);”語句時(shí),構(gòu)造函數(shù)動(dòng)態(tài)地分配存儲(chǔ)空間,并將返回的地址賦給對(duì)象s1的成員變量strValue,然后把“Test”拷貝到這塊空間中:執(zhí)行語句“Strings2(s1);”時(shí),系統(tǒng)將調(diào)用默認(rèn)的復(fù)制構(gòu)造函數(shù),負(fù)責(zé)將對(duì)象s1的數(shù)據(jù)成員strValue中存放的地址值賦值給對(duì)象s2的數(shù)據(jù)成員strValue:當(dāng)遇到對(duì)象的生命期結(jié)束需要撤銷對(duì)象時(shí),首先由s2對(duì)象調(diào)用析構(gòu)函數(shù),將strValue成員所指向的字符串“Test”所在的動(dòng)態(tài)空間釋放:在對(duì)象s1自動(dòng)調(diào)用析構(gòu)函數(shù)之前,對(duì)象s1的數(shù)據(jù)成員strValue指向已釋放的內(nèi)存空間,因此在s1調(diào)用析構(gòu)函數(shù)時(shí),無法正確執(zhí)行析構(gòu)函數(shù)代碼“delete[]strValue”,從而導(dǎo)致出錯(cuò)計(jì)算機(jī)學(xué)院李衛(wèi)明編譯器合成的拷貝構(gòu)造也稱為淺復(fù)制構(gòu)造,對(duì)于采用動(dòng)態(tài)分配的鏈集合類并不適用。鏈集合類需要重載拷貝構(gòu)造函數(shù),才能達(dá)到正確效果,如圖3.4所示。這樣重載的拷貝構(gòu)造函數(shù)稱為深復(fù)制構(gòu)造。深復(fù)制構(gòu)造完成后A、B對(duì)象相互獨(dú)立存在,互不影響??截悩?gòu)造函數(shù)形式如下:CSet(constCSet&rhs);圖3.4鏈集合類重載的拷貝構(gòu)造效果圖計(jì)算機(jī)學(xué)院李衛(wèi)明定義字符串類復(fù)制構(gòu)造函數(shù)解決動(dòng)態(tài)內(nèi)存的問題定義復(fù)制構(gòu)造函數(shù),采用深層復(fù)制,通過復(fù)制指針數(shù)據(jù)成員strValue所指向的動(dòng)態(tài)空間中的內(nèi)容。這樣,兩個(gè)對(duì)象的指針成員strValue就擁有不同的地址值,指向不同的動(dòng)態(tài)存儲(chǔ)空間,但兩個(gè)動(dòng)態(tài)空間中的內(nèi)容完全一樣。計(jì)算機(jī)學(xué)院李衛(wèi)明//定義復(fù)制構(gòu)造函數(shù)避免默認(rèn)構(gòu)造函數(shù)的副作用。classString{private:

char*strValue; //串值public:

String(char*s="") //構(gòu)造函數(shù)

{ if(s==NULL)s=""; //將空指針轉(zhuǎn)化為空串

strValue=newchar[strlen(s)+1];//分配存儲(chǔ)空間

strcpy(strValue,s); //復(fù)制串值

} String(constString©) //復(fù)制構(gòu)造函數(shù)

{ strValue=newchar[strlen(copy.strValue)+1];//分配空間

strcpy(strValue,copy.strValue); //復(fù)制串值

} ~String(){delete[]strValue;} //析構(gòu)函數(shù)

voidShow(){cout<<strValue<<endl;} //顯示串 };intmain() //主函數(shù)main(){ Strings1(“test"); //調(diào)用普通構(gòu)造函數(shù)的生成對(duì)象s1 Strings2(s1); //調(diào)用復(fù)制構(gòu)造函數(shù)的生成對(duì)象s2

s1.Show(); //顯示串s1 s2.Show(); //顯示串s2 ……}程序運(yùn)行時(shí)屏幕輸出如下:testtest請按任意鍵繼續(xù)...計(jì)算機(jī)學(xué)院李衛(wèi)明

使用復(fù)制構(gòu)造的三種情形

一.構(gòu)造對(duì)象時(shí)使用同類對(duì)象顯式初始化: CSet B;...CSet A=B;或CSetA(B);或CSetA{B};//C++11二.函數(shù)調(diào)用參數(shù)調(diào)用采用傳值方式時(shí),實(shí)參采用拷貝構(gòu)造方式傳遞給形參voidFun(CSample obj); ...Fun(someObj);三.函數(shù)調(diào)用返回值對(duì)象時(shí)queue<int> GetIntQueue(){ queue<int>inputQueue;.... returninputQueue;}此外,作為對(duì)象的成員(子對(duì)象)隨宿主對(duì)象的復(fù)制構(gòu)造而復(fù)制構(gòu)造計(jì)算機(jī)學(xué)院李衛(wèi)明如用戶沒有為一個(gè)類重載賦值運(yùn)算符,編譯程序?qū)⑸梢粋€(gè)默認(rèn)賦值運(yùn)算符函數(shù),把源對(duì)象的數(shù)據(jù)成員逐個(gè)賦值給目的對(duì)象的相應(yīng)數(shù)據(jù)成員.對(duì)于一般的類,使用默認(rèn)賦值運(yùn)算符函數(shù)都能正常地工作,但當(dāng)一個(gè)類中包含有指針類型的數(shù)據(jù)成員,并且通過指針在構(gòu)造函數(shù)中動(dòng)態(tài)申請了存儲(chǔ)空間,在析構(gòu)函數(shù)中通過指針釋放了動(dòng)態(tài)存儲(chǔ)空間,這種情況可能會(huì)出現(xiàn)運(yùn)行時(shí)錯(cuò)誤;類似于拷貝構(gòu)造情況。一般地,如果一個(gè)類重載了拷貝構(gòu)造函數(shù),而且使用了=,那么也需要重載=。計(jì)算機(jī)學(xué)院李衛(wèi)明3.2.2 拷貝賦值

圖3.5鏈集合類對(duì)象的賦值前狀態(tài)示意圖圖3.6B=A;鏈集合類編譯器合成的拷貝賦值效果圖計(jì)算機(jī)學(xué)院李衛(wèi)明//使用賦值運(yùn)算符出現(xiàn)運(yùn)行時(shí)錯(cuò)誤的示例。classString{private:

char*strValue; //串值public:

String(constchar*s="") //構(gòu)造函數(shù)

{ if(s==NULL)s=""; //將空指針轉(zhuǎn)化為空串

strValue=newchar[strlen(s)+1]; //分配存儲(chǔ)空間

strcpy(strValue,s); //復(fù)制串值

} String(constString©) //復(fù)制構(gòu)造函數(shù)

{ strValue=newchar[strlen(copy.strValue)+1];//分配空間

strcpy(strValue,copy.strValue); //復(fù)制串值

} ~String(){delete[]strValue;} //析構(gòu)函數(shù)

voidShow()const{cout<<strValue<<endl;} //顯示串};……計(jì)算機(jī)學(xué)院李衛(wèi)明intmain() //主函數(shù)main(){ Strings1("try"),s2; //定義對(duì)象

s2=s1; //使用默認(rèn)賦值運(yùn)算符函數(shù)

s1.Show(); //顯示串s1 s2.Show(); //顯示串s2}程序運(yùn)行時(shí)屏幕可能輸出如下:trytry請按任意鍵繼續(xù)...當(dāng)用戶按任一鍵時(shí),屏幕將會(huì)顯示類似DebugAssertionFailed!的錯(cuò)誤計(jì)算機(jī)學(xué)院李衛(wèi)明在執(zhí)行“Strings1(“try”);”語句時(shí),構(gòu)造函數(shù)動(dòng)態(tài)地分配存儲(chǔ)空間,并將返回的地址賦給對(duì)象s1的數(shù)據(jù)成員strValue,然后把“try”拷貝到這塊空間中執(zhí)行語句“s2=s1;”時(shí),由于沒有為類String重載賦值運(yùn)算符,系統(tǒng)將調(diào)用默認(rèn)賦值運(yùn)算符函數(shù),負(fù)責(zé)將對(duì)象s1的數(shù)據(jù)成員strValue中存放的地址值賦值給對(duì)象s2的數(shù)據(jù)成員strValue對(duì)象s1復(fù)制給對(duì)象s2的僅是其數(shù)據(jù)成員strValue的值,并沒有把strValue指向的動(dòng)態(tài)存儲(chǔ)空間進(jìn)行復(fù)制,當(dāng)遇到對(duì)象的生命期結(jié)束需要撤銷對(duì)象時(shí),首先由s2對(duì)象調(diào)用析構(gòu)函數(shù),將strValue成員所指向的字符串“try”所在的動(dòng)態(tài)空間釋放在對(duì)象s1自動(dòng)調(diào)用析構(gòu)函數(shù)之前,對(duì)象s1的數(shù)據(jù)成員strValue指向已釋放的內(nèi)存空間,因此在s1調(diào)用析構(gòu)函數(shù)時(shí),無法正確執(zhí)行析構(gòu)函數(shù)代碼“delete[]strValue”,從而導(dǎo)致出錯(cuò)。計(jì)算機(jī)學(xué)院李衛(wèi)明編譯器合成的拷貝賦值運(yùn)算也稱為淺拷貝賦值,對(duì)于采用動(dòng)態(tài)分配的類并不適用。

鏈集合類需要重載賦值運(yùn)算符,才能達(dá)到正確效果,釋放B集合對(duì)象的原鏈表,再將A對(duì)象的鏈表復(fù)制過來,復(fù)制后狀態(tài)如圖3.7所示。這樣重載的賦值運(yùn)算稱為深拷貝賦值。深拷貝賦值完成后A、B對(duì)象相互獨(dú)立存在,互不影響??截愘x值運(yùn)算符重載形式如下:CSet&operator=(constCSet&rhs);圖3.7鏈集合類重載的拷貝賦值效果圖計(jì)算機(jī)學(xué)院李衛(wèi)明字符串類重載賦值運(yùn)算符對(duì)于字符串類,也應(yīng)重載賦值運(yùn)算符,復(fù)制指針數(shù)據(jù)成員strValue所指向的動(dòng)態(tài)空間中的內(nèi)容。這樣,兩個(gè)對(duì)象的指針成員strValue就擁有不同的地址值,指向不同的動(dòng)態(tài)存儲(chǔ)空間:計(jì)算機(jī)學(xué)院李衛(wèi)明賦值運(yùn)算符=重載的一般形式C++規(guī)定賦值運(yùn)算符=只能重載為類的成員函數(shù),一般重載格式為:類名&類名::operator=(const類名&源對(duì)象){ if(this!=&源對(duì)象) { //目的對(duì)象與源對(duì)象不是同一個(gè)對(duì)象

…… //復(fù)制被被賦值對(duì)象

} return*this; //返回目的對(duì)象}計(jì)算機(jī)學(xué)院李衛(wèi)明//重載賦值運(yùn)算符避免使用默認(rèn)賦值運(yùn)算符的嚴(yán)重問題。classString{private:

char*strValue; //串值public:

String(constchar*s="") //構(gòu)造函數(shù)

{ if(s==NULL)s=""; //將空指針轉(zhuǎn)化為空串

strValue=newchar[strlen(s)+1];//分配存儲(chǔ)空間

strcpy(strValue,s); //復(fù)制串值

} String(constString©) //復(fù)制構(gòu)造函數(shù)

{ strValue=newchar[strlen(copy.strValue)+1];//分配空間

strcpy(strValue,copy.strValue); //復(fù)制串值

} String&operator=(constString©); //重載賦值運(yùn)算符

~String(){delete[]strValue;} //析構(gòu)函數(shù)

voidShow()const{cout<<strValue<<endl;} //顯示串};……計(jì)算機(jī)學(xué)院李衛(wèi)明String&String::operator=(constString©) //重載賦值運(yùn)算符{ if(this!=©) {//目的對(duì)象與源對(duì)象不是同一個(gè)對(duì)象 delete[]strValue;

strValue=newchar[strlen(copy.strValue)+1];//分配空間

strcpy(strValue,copy.strValue);//復(fù)制串值

} return*this; //返回目的對(duì)象}intmain() //主函數(shù)main(){ Strings1("try"),s2; //定義對(duì)象

s2=s1; //使用重載賦值運(yùn)算符

s1.Show(); //顯示串s1 s2.Show(); //顯示串s2}程序運(yùn)行時(shí)屏幕輸出如下:trytry請按任意鍵繼續(xù)...計(jì)算機(jī)學(xué)院李衛(wèi)明//重載賦值運(yùn)算符的另一種好方法,更符合異常安全特性?!璖tring&String::operator=(constString&rhs) //重載賦值運(yùn)算符{ String copy(rhs); //此時(shí),如果發(fā)生異常,不影響原對(duì)象 swap(strValue,copy.strValue);

return*this; //返回目的對(duì)象}intmain() //主函數(shù)main(){ Strings1("try"),s2; //定義對(duì)象

s2=s1; //使用重載賦值運(yùn)算符

s1.Show(); //顯示串s1 s2.Show(); //顯示串s2}程序運(yùn)行時(shí)屏幕輸出如下:trytry請按任意鍵繼續(xù)...計(jì)算機(jī)學(xué)院李衛(wèi)明

我們通過重載拷貝構(gòu)造函數(shù)和拷貝賦值解決了具有動(dòng)態(tài)分配類的對(duì)象的拷貝構(gòu)造和賦值問題,可保證我們程序的正確執(zhí)行效果。

考慮如下語句:CSetC=A.Union(B);執(zhí)行過程中集合A與集合B進(jìn)行并運(yùn)算,結(jié)果先保存在一個(gè)局部對(duì)象中,并運(yùn)算返回后,編譯器通過重載的拷貝構(gòu)造將結(jié)果局部對(duì)象復(fù)制給臨時(shí)匿名返回值集合對(duì)象,局部對(duì)象消失,執(zhí)行析構(gòu)函數(shù),釋放鏈表,最后,再根據(jù)匿名返回值對(duì)象復(fù)制構(gòu)造對(duì)象C,C得到正確結(jié)果,匿名返回值對(duì)象消失,執(zhí)行析構(gòu)函數(shù),釋放鏈表。這個(gè)過程結(jié)果正確,也沒有造成內(nèi)存泄漏,但內(nèi)含若干次不必要的鏈表復(fù)制和釋放,造成極大性能浪費(fèi),效率極低。計(jì)算機(jī)學(xué)院李衛(wèi)明3.2.3 C++11移動(dòng)構(gòu)造

C++11之前主要通過編譯器優(yōu)化解決這個(gè)效率問題,但有些情況下,編譯器無法解決這一效率問題,如交換2個(gè)鏈集合對(duì)象語句:CSettmp=A;A=B;B=tmp;這些語句執(zhí)行過程中存在多次鏈表復(fù)制和銷毀,極大影響了執(zhí)行效率。計(jì)算機(jī)學(xué)院李衛(wèi)明C++11引入了移動(dòng)構(gòu)造和移動(dòng)賦值用于解決這一類問題,移動(dòng)構(gòu)造也稱轉(zhuǎn)移構(gòu)造。

根據(jù)A對(duì)象移動(dòng)構(gòu)造B對(duì)象前,A、B對(duì)象狀態(tài)與圖3.2所示拷貝構(gòu)造時(shí)狀態(tài)一樣。A對(duì)象事后無需使用,只需簡單的將A對(duì)象鏈表轉(zhuǎn)移給B對(duì)象,A對(duì)象不再擁有鏈表,A對(duì)象指針置nullptr,消失時(shí)析構(gòu)函數(shù)不再釋放鏈表,實(shí)現(xiàn)高效完美的資源即鏈表的轉(zhuǎn)移,這也是轉(zhuǎn)移構(gòu)造名稱的由來。圖3.8是轉(zhuǎn)移后的效果圖。圖3.2鏈集合類的移動(dòng)構(gòu)造前效果圖圖3.8鏈集合類的移動(dòng)構(gòu)造效果圖計(jì)算機(jī)學(xué)院李衛(wèi)明如果我們用臨時(shí)對(duì)象給對(duì)象賦值,也存在類似情況??紤]如下語句:C=A.Union(B);執(zhí)行過程中集合A與集合B并運(yùn)算,結(jié)果先保存在一個(gè)局部對(duì)象中,并運(yùn)算返回后,編譯器先將保存在局部復(fù)制一個(gè)臨時(shí)匿名返回值集合對(duì)象,局部對(duì)象消失,執(zhí)行析構(gòu)函數(shù),釋放鏈表,最后,再根據(jù)匿名返回值對(duì)象賦值給對(duì)象C,C得到正確結(jié)果,匿名返回值對(duì)象消失,執(zhí)行析構(gòu)函數(shù),釋放鏈表。這個(gè)執(zhí)行過程結(jié)果正確,也沒有造成內(nèi)存泄漏,但內(nèi)含若干次不必要的鏈表復(fù)制、賦值和釋放,造成極大性能浪費(fèi),效率極低。同樣,C++98主要通過編譯器優(yōu)化解決這個(gè)效率問題,但有些情況下,編譯器無法解決這一效率問題,如前面交換2個(gè)鏈集合對(duì)象。C++11引入了移動(dòng)賦值用于解決這一問題,移動(dòng)賦值也稱轉(zhuǎn)移賦值。計(jì)算機(jī)學(xué)院李衛(wèi)明3.2.4 C++11移動(dòng)賦值將對(duì)象A移動(dòng)賦值給B時(shí),A、B對(duì)象狀態(tài)與移動(dòng)構(gòu)造時(shí)不同,與圖3.5所示復(fù)制賦值時(shí)相同,賦值前對(duì)象B已存在,A、B對(duì)象事先均擁有鏈表。

圖3.5鏈集合類的移動(dòng)賦值前效果圖計(jì)算機(jī)學(xué)院李衛(wèi)明如果A對(duì)象事后無需使用,只需先釋放B對(duì)象的鏈表,再將A對(duì)象鏈表轉(zhuǎn)移給B對(duì)象,A對(duì)象不再擁有鏈表,A對(duì)象指針置nullptr,消失時(shí)析構(gòu)函數(shù)不再釋放鏈表,實(shí)現(xiàn)高效完美的資源即鏈表的轉(zhuǎn)移。圖3.9是這樣方法處理后的移動(dòng)賦值效果示意圖。

圖3.9鏈集合類的移動(dòng)賦值效果圖1

計(jì)算機(jī)學(xué)院李衛(wèi)明更好、更簡單的方法。

當(dāng)前對(duì)象廢棄的資源(鏈表)轉(zhuǎn)移給臨時(shí)對(duì)象A,將來臨時(shí)對(duì)象消失時(shí)會(huì)執(zhí)行析構(gòu)函數(shù),完成資源的釋放。圖3.10是采用這一處理方法的移動(dòng)賦值效果示意圖。圖3.10鏈集合類的移動(dòng)賦值效果圖2計(jì)算機(jī)學(xué)院李衛(wèi)明轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移賦值運(yùn)算符一般形式如果傳進(jìn)來的對(duì)象是一個(gè)臨時(shí)對(duì)象(馬上就銷毀),我們自然希望能夠繼續(xù)使用這個(gè)臨時(shí)對(duì)象的空間,這樣可以節(jié)省申請空間和復(fù)制的時(shí)間。C++11支持新移動(dòng)構(gòu)造和移動(dòng)賦值機(jī)制,它將臨時(shí)對(duì)象(馬上就銷毀)資源轉(zhuǎn)移至新對(duì)象,顯著提高執(zhí)行效率。語法形式如下:CSample(CSample&&rhs);CSample&operator=CSample(CSample&&rhs);保證不會(huì)拋出異常時(shí),最好聲明:CSample(CSample&&rhs)noexcept;CSample&operator=CSample(CSample&&rhs)noexcept;現(xiàn)代C++STL庫已據(jù)此作修正.noexcept聲明無異常,有利于容器內(nèi)使用時(shí)優(yōu)化性能(如vector擴(kuò)展空間時(shí),會(huì)調(diào)用無異常的移動(dòng)版本來完成搬動(dòng),不會(huì)調(diào)用有異常的移動(dòng)版本),如有異常,不可聲明noexcept。

本章第4節(jié)鏈集合向量空間擴(kuò)充探討,說明了需要聲明noexcept的理由。移動(dòng)構(gòu)造和移動(dòng)賦值后必須保證源對(duì)象可正常析構(gòu),一般應(yīng)該可以正常重新賦值(內(nèi)容已變空)。計(jì)算機(jī)學(xué)院李衛(wèi)明#include<iostream>#include<memory>#include<cstring>usingnamespacestd;classString{private: char*strValue; //串值public: String(constchar*s=""); //構(gòu)造函數(shù) String(constString©); //復(fù)制構(gòu)造函數(shù) String(String&©)noexcept; //移動(dòng)構(gòu)造函數(shù) String&operator=(constString©); //重載賦值運(yùn)算符 String&operator=(String&©)noexcept;//移動(dòng)賦值 StringReverse()const;

//返回逆序字符串 ~String(){delete[]strValue;} //析構(gòu)函數(shù) voidShow()const; //顯示串};計(jì)算機(jī)學(xué)院李衛(wèi)明String::String(constchar*s) //構(gòu)造函數(shù){if(s==NULL)s=""; //將空指針轉(zhuǎn)化為空串strValue=newchar[strlen(s)+1];//分配存儲(chǔ)空間strcpy(strValue,s); //復(fù)制串值}String::String(constString©) //復(fù)制構(gòu)造函數(shù){strValue=newchar[strlen(copy.strValue)+1];//分配空間strcpy(strValue,copy.strValue); //復(fù)制串值}String::String(String&©) noexcept //移動(dòng)構(gòu)造函數(shù){strValue=copy.strValue;//轉(zhuǎn)移字符串值copy.strValue=NULL; //已轉(zhuǎn)移,避免再次delete}計(jì)算機(jī)學(xué)院李衛(wèi)明String&String::operator=(constString©) //重載賦值運(yùn)算符{ if(this!=©) {//目的對(duì)象與源對(duì)象不是同一個(gè)對(duì)象 delete[]strValue; strValue=newchar[strlen(copy.strValue)+1];//分配空間 strcpy(strValue,copy.strValue);//復(fù)制串值 } return*this; //返回目的對(duì)象}String&String::operator=(String&©)noexcept{ //移動(dòng)賦值char*t=strValue;strValue=copy.strValue;copy.strValue=t;//交換字符串值return*this;}計(jì)算機(jī)學(xué)院李衛(wèi)明StringString::Reverse()const//返回逆序字符串{StringstrResult(*this);intiLength=strlen(strValue);for(inti=0;i<iLength/2;i++){charc=strResult.strValue[i];strResult.strValue[i]=strResult.strValue[iLength-1-i];strResult.strValue[iLength-1-i]=c;}returnstrResult;}voidString::Show()const//顯示串{if(strValue)cout<<strValue<<endl;elsecout<<endl;}

計(jì)算機(jī)學(xué)院李衛(wèi)明intmain() //主函數(shù)main(){ Strings1("try"),s2,s3; //定義對(duì)象 s2=s1; //使用重載賦值運(yùn)算符 s3=s1.Reverse();//返回臨時(shí)字符串對(duì)象使用移動(dòng)賦值 s1.Show(); //顯示串s1 s2.Show(); //顯示串s2 s3.Show(); //顯示串s2 Strings4(std::move(s1));

//強(qiáng)制使用移動(dòng)構(gòu)造,頭文件 //<utility> s1.Show(); //顯示串s1 s4.Show(); //顯示串s4}計(jì)算機(jī)學(xué)院李衛(wèi)明

一般來說,沒有使用動(dòng)態(tài)分配的類無需定義拷貝控制函數(shù);具有動(dòng)態(tài)分配的類需要同時(shí)定義組成拷貝控制的五個(gè)函數(shù):拷貝構(gòu)造函數(shù)、拷貝賦值運(yùn)算符、析構(gòu)函數(shù)、移動(dòng)構(gòu)造函數(shù)、移動(dòng)賦值運(yùn)算符,拷貝控制函數(shù)中析構(gòu)函數(shù)用來釋放對(duì)象占用的資源;后兩個(gè)函數(shù)是C++11新引入的,這個(gè)法則就是通常所述的三/五法則。如果類沒有定義自己的拷貝構(gòu)造函數(shù)、拷貝賦值、移動(dòng)構(gòu)造函數(shù)、移動(dòng)賦值,編譯器會(huì)給所有的類提供合成的拷貝構(gòu)造函數(shù)、拷貝賦值、移動(dòng)構(gòu)造函數(shù)、移動(dòng)賦值。如果類已含有復(fù)制構(gòu)造、復(fù)制賦值、移動(dòng)構(gòu)造、移動(dòng)賦值函數(shù)之一,編譯器不會(huì)合成其它幾個(gè)函數(shù)。當(dāng)然,如果由于類具有無法拷貝構(gòu)造、拷貝賦值、移動(dòng)構(gòu)造、移動(dòng)賦值的數(shù)據(jù)成員時(shí),編譯器也無法合成相應(yīng)函數(shù)。除已刪除相應(yīng)功能的特殊容器外,C++11標(biāo)準(zhǔn)庫提供的容器都已實(shí)現(xiàn)拷貝構(gòu)造、拷貝賦值、移動(dòng)構(gòu)造、移動(dòng)賦值和析構(gòu)這五個(gè)拷貝控制函數(shù)?,F(xiàn)代C++程序設(shè)計(jì)需要表示復(fù)雜對(duì)象狀態(tài)時(shí),應(yīng)該優(yōu)先使用STL提供的容器作為類成員,這樣就無需使用動(dòng)態(tài)分配,這樣的類也就無需定義拷貝控制有關(guān)函數(shù),直接由編譯器合成即可。計(jì)算機(jī)學(xué)院李衛(wèi)明3.2.5 std::move應(yīng)用使用類的拷貝控制函數(shù)時(shí),一般由編譯器決定實(shí)際使用的是拷貝版還是移動(dòng)版,針對(duì)臨時(shí)對(duì)象如函數(shù)返回值對(duì)象,編譯器使用移動(dòng)構(gòu)造或移動(dòng)賦值,對(duì)于具有名字的普通對(duì)象,編譯器會(huì)使用拷貝構(gòu)造或拷貝賦值。如果需要對(duì)具有名字的普通對(duì)象使用移動(dòng)構(gòu)造或移動(dòng)賦值,C++標(biāo)準(zhǔn)庫提供了std::move函數(shù)模板,視指定具名對(duì)象為臨時(shí)對(duì)象,拷貝或賦值時(shí)使用移動(dòng)構(gòu)造或移動(dòng)賦值。std::move可適合各類對(duì)象,使用方法與普通函數(shù)基本相同,關(guān)于函數(shù)模板,詳見第6章。如下述函數(shù)交換2個(gè)鏈集合對(duì)象時(shí),使用移動(dòng)構(gòu)造和移動(dòng)賦值完成交換,具有非常高的效率,算法時(shí)間復(fù)雜性為O(1)。voidswapSet(CSet&A,CSet&B){CSettmp=std::move(A);//根據(jù)A移動(dòng)構(gòu)造tmpA=std::move(B); //B移動(dòng)賦值給AB=std::move(tmp); //tmp移動(dòng)賦值給B}實(shí)際無需自己定義上述函數(shù),C++標(biāo)準(zhǔn)庫提供了函數(shù)模板swap??梢灾苯诱{(diào)用:swap(A,B);完成對(duì)象交換。只要對(duì)象的類具有高效率、無異常的移動(dòng)構(gòu)造、移動(dòng)賦值,這樣的交換就是高效、無異常的。計(jì)算機(jī)學(xué)院李衛(wèi)明3.3 典型范例——鏈表表示的集合類實(shí)現(xiàn)樣例Ex3.1綜合以上分析,設(shè)計(jì)實(shí)現(xiàn)了功能較完備的集合類,集合內(nèi)元素采用帶頭結(jié)點(diǎn)單鏈表表示,集合元素類型為整形,元素遞增排序。樣例里集合類支持集合顯示和元素增加、查詢,并支持集合并運(yùn)算,運(yùn)算結(jié)果返回集合對(duì)象;樣例設(shè)計(jì)和實(shí)現(xiàn)了拷貝構(gòu)造和拷貝賦值、移動(dòng)構(gòu)造和移動(dòng)賦值、析構(gòu)函數(shù)五個(gè)拷貝控制函數(shù),不會(huì)有內(nèi)存泄漏。樣例利用該集合類完成了基本測試,測試程序輸入數(shù)據(jù)開始為兩個(gè)正整數(shù)m,n;后續(xù)m個(gè)整數(shù)構(gòu)成集合A,再后續(xù)n個(gè)整數(shù)構(gòu)成集合B,輸出集合A、B和他們的并集。限于篇幅,具體樣例代碼和介紹參見附件。計(jì)算機(jī)學(xué)院李衛(wèi)明3.4 *鏈集合向量空間擴(kuò)充探討STL提供了程序設(shè)計(jì)中廣泛使用的向量類模板。如果向量元素類型為鏈集合時(shí),STL向量類模板就可以產(chǎn)生鏈集合向量模板類,它的實(shí)例就是鏈集合向量,是一種復(fù)雜的典型容器對(duì)象。鏈集合向量可以存放若干鏈集合對(duì)象,并且具有根據(jù)需要調(diào)用pushback在尾部添加鏈集合對(duì)象的能

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論