版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第3章拷貝控制本章主要內容:對象傳遞、復制和賦值 具有動態(tài)分配的類
拷貝構造 拷貝賦值
C++11移動構造
C++11移動賦值 std::move應用 典型范例——鏈表表示的集合類實現(xiàn)
*鏈集合向量空間擴充探討
計算機學院李衛(wèi)明對象的傳遞涉及對象的復制、賦值或轉移。拷貝控制函數(shù)由類的拷貝構造、移動構造、拷貝賦值、移動賦值和析構五個函數(shù)組成。前面我們設計和實現(xiàn)的類,數(shù)據(jù)成員類型都是內置數(shù)據(jù)類型、固定大小數(shù)組、或類類型,這樣的類類型一般無需定義上述五個拷貝控制函數(shù),不影響這些類型對象的復制、賦值、轉移,也不影響它們的正確析構。像集合、棧、字符串、向量等具有比較復雜的狀態(tài)的對象,往往需要采用動態(tài)分配,如用鏈表存儲集合內的元素、用動態(tài)分配的連續(xù)空間存放向量元素等,這些類需要定義和實現(xiàn)五個拷貝控制函數(shù)。
標準庫提供的vector、string、list等類模板就是在動態(tài)分配基礎上實現(xiàn)的。計算機學院李衛(wèi)明3.1 對象傳遞、復制和賦值C++函數(shù)參數(shù)可以傳遞對象的引用、對象的地址、對象數(shù)組,也可以傳遞對象復制的副本。C++函數(shù)參數(shù)傳遞對象的引用:實參是對象名,形參本質上是實參的別名,實參和形參是同一個對象。對于大型對象,引用傳遞具有極高傳遞效率,是C++函數(shù)間最為普遍的參數(shù)傳遞方式,如果希望函數(shù)處理期間對象不發(fā)生變化,一般聲明常引用。
假設CSet是一個類,常引用聲明和調用形式如下: CSetUnion(constCSet&rhs); C=A.Union(B);計算機學院李衛(wèi)明C++函數(shù)參數(shù)傳遞對象的地址:實參是對象的地址,形參是類指針類型,形參指向實參對象。對于大型對象,一樣具有極高傳遞效率,但不如引用傳遞方式直接明了,較少采用,如果希望函數(shù)處理期間對象不發(fā)生變化,可以聲明指針所指對象不可變。傳遞對象地址時,函數(shù)聲明和調用形式如下: CSetUnion(constCSet*pSet); C=A.Union(&B);計算機學院李衛(wèi)明C++函數(shù)參數(shù)傳遞對象數(shù)組:實參是對象數(shù)組名,形參是元素具有類類型的數(shù)組,形參實際復制了代表數(shù)組起始地址的實參值。對于大型對象數(shù)組,具有極高傳遞效率,一般傳遞對象數(shù)組需求較少。傳遞對象數(shù)組時,函數(shù)聲明和調用形式如下:doubleTotalSize(CCircleallCircles[],intiCount);x=TotalSize(circles,n);//circles是CCircle對象組成的數(shù)組計算機學院李衛(wèi)明C++函數(shù)參數(shù)采用傳值形式:實參是對象名,形參是根據(jù)實參對象在運行棧上新復制構造的對象,構造完成后,形參和實參是2個獨立的對象,形參變化,實參不變。對于大型對象,復制效率較低,一般在確實必要時才采用。函數(shù)聲明和調用形式如下: voidDoSomeThing(CSampleobj); DoSomeThing(obj);計算機學院李衛(wèi)明與參數(shù)傳遞類似,關于函數(shù)返回值類型,也有返回對象引用、對象指針和對象值類型三種方式。對于局部對象,由于函數(shù)執(zhí)行完畢后局部對象會析構,因此,不可返回局部對象的引用或指針,否則,根據(jù)函數(shù)返回結果去訪問對象會導致不確定的錯誤結果。當函數(shù)返回對象值類型時,編譯器會通過復制構造一個臨時副本對象返回,因此,函數(shù)返回值具有對象類型時,函數(shù)可以返回局部對象。計算機學院李衛(wèi)明C++程序設計中,還經常需要顯式根據(jù)對象A復制構造一個新對象B,如:CSampleB(A);或CSampleB{A};或CSampleB=A;除上述參數(shù)傳值、函數(shù)返回對象和顯式復制構造外,如果對象作為另一個類型大對象的子對象,隨著大對象的復制構造,子對象也會復制構造。對象的復制是通過復制構造函數(shù)完成的,復制構造函數(shù)也稱為拷貝構造函數(shù)。
通常,C++編譯器會自動合成復制構造函數(shù),合成的復制構造函數(shù)實際效果是逐個數(shù)據(jù)成員的復制。如果一個類的所有數(shù)據(jù)成員類型都是內置數(shù)據(jù)類型、固定大小數(shù)組,或類類型,編譯器合成的復制構造函數(shù)就是我們需要的效果,如同前面簡單集合類所示,對于這樣的類,我們無需特殊處理就可以使用復制構造。計算機學院李衛(wèi)明類似情況,C++程序中經常需要給對象賦值,如:CSampleA,B;...B=A;上述賦值語句將對象A賦值給對象B。執(zhí)行賦值前已存在2個獨立對象A、B,賦值完成后2個對象狀態(tài)相同:對象B變成對象A一樣的狀態(tài)。注意,這與前述對象A復制構造對象B不同,復制構造前只有一個對象A存在,復制構造完成后有2個獨立且狀態(tài)一樣的對象。對象的賦值是通過賦值運算符完成的。通常,C++編譯器會自動合成賦值運算符,合成的賦值運算符實際效果是逐個數(shù)據(jù)成員的賦值。如果一個類的所有數(shù)據(jù)成員類型都是內置數(shù)據(jù)類型、固定大小數(shù)組,或類類型,編譯器合成的賦值運算符就是我們需要的效果,如同前面簡單集合類所示,對于這樣的類,我們無需特殊處理就可以使用賦值。計算機學院李衛(wèi)明//使用缺省復制構造函數(shù)和賦值運算符的示例。classTime{private:
inthour; //時
intminute; //分
intsecond; //秒public:
Time(inth=0,intm=0,ints=0) :hour(h),minute(m),second(s){} //構造函數(shù)
voidSet(inth,intm,ints) //設置時間
{hour=h;minute=m;second=s;} voidShow()const //顯示時間
{cout<<hour<<":"<<minute<<":"<<second<<endl;}};intmain() //主函數(shù)main(){ Timet1(6,16,18),t2; //構造函數(shù)的參數(shù)都采用默認值
t1.Show(); //顯示時間6:16:18 t2=t1; //利用合成賦值函數(shù)賦值 t2.Show(); //顯示時間6:16:18 Timet3(t1); //利用合成拷貝構造函數(shù)構造對象t3 t3.Show(); //顯示時間6:16:18}計算機學院李衛(wèi)明3.2 具有動態(tài)分配的類C++程序中,并非所有類的數(shù)據(jù)成員類型都是內置數(shù)據(jù)類型、固定大小數(shù)組,還經常遇到一些如集合、棧、字符串、向量等對象,具有比較復雜的狀態(tài),為表示這些狀態(tài)復雜多變的對象,往往需要采用動態(tài)分配,如用鏈表表示集合內的元素、用動態(tài)分配的連續(xù)空間存放向量內容等等,如標準庫的vector、string、set等類模板就是在動態(tài)分配基礎上實現(xiàn)的。對于具有動態(tài)分配的類,使用編譯器合成的拷貝構造函數(shù)和復制賦值運算符,運行時會導致嚴重問題,但編譯器并不會發(fā)出警告。計算機學院李衛(wèi)明下面以鏈集合類和簡單字符串類為例,分析需要動態(tài)分配的類如何設計和實現(xiàn)拷貝控制函數(shù)。
鏈集合類用帶頭結點的單鏈表存儲集合內元素,不限定集合元素個數(shù)。圖3.1鏈集合對象內存狀態(tài)示意圖計算機學院李衛(wèi)明
圖3.2鏈集合類對象的拷貝構造前狀態(tài)示意圖圖3.3編譯器合成的鏈集合類拷貝構造效果圖計算機學院李衛(wèi)明3.2.1 拷貝構造使用默認復制構造函數(shù)可能出現(xiàn)運行時錯誤默認復制構造函數(shù)只簡單地將源對象的數(shù)據(jù)成員的值復制給目的對象的相應數(shù)據(jù)成員當類具有指針成員時,缺省拷貝構造函數(shù)為淺層復制。當一個類中包含指針類型的數(shù)據(jù)成員,并且通過指針在構造函數(shù)中動態(tài)申請了存儲空間,在析構函數(shù)中通過指針釋放了動態(tài)存儲空間,這種情況下默認復制構造函數(shù)將會出現(xiàn)運行時錯誤。A對象指針成員B對象淺層復制計算機學院李衛(wèi)明//使用默認復制構造函數(shù)出現(xiàn)運行時錯誤的示例。classString{private:
char*strValue; //串值public:
String(char*s="") //構造函數(shù)
{ if(s==NULL)s=""; //將空指針轉化為空串
strValue=newchar[strlen(s)+1]; //分配存儲空間
strcpy(strValue,s); //復制串值
} ~String(){delete[]strValue;} //析構函數(shù)
voidShow(){cout<<strValue<<endl;} //顯示串
};intmain() //主函數(shù)main(){ Strings1(“test"); //調用普通構造函數(shù)的生成對象s1 Strings2(s1); //調用默認復制構造函數(shù)的生成對象s2 s1.Show(); //顯示串s1 s2.Show(); //顯示串s2}程序運行時可能的屏幕輸出如下:testtest請按任意鍵繼續(xù)...當用戶按任一鍵時,屏幕將會顯示類似DebugAssertionFailed!的錯誤計算機學院李衛(wèi)明在執(zhí)行“Strings1(“test”);”語句時,構造函數(shù)動態(tài)地分配存儲空間,并將返回的地址賦給對象s1的成員變量strValue,然后把“Test”拷貝到這塊空間中:執(zhí)行語句“Strings2(s1);”時,系統(tǒng)將調用默認的復制構造函數(shù),負責將對象s1的數(shù)據(jù)成員strValue中存放的地址值賦值給對象s2的數(shù)據(jù)成員strValue:當遇到對象的生命期結束需要撤銷對象時,首先由s2對象調用析構函數(shù),將strValue成員所指向的字符串“Test”所在的動態(tài)空間釋放:在對象s1自動調用析構函數(shù)之前,對象s1的數(shù)據(jù)成員strValue指向已釋放的內存空間,因此在s1調用析構函數(shù)時,無法正確執(zhí)行析構函數(shù)代碼“delete[]strValue”,從而導致出錯計算機學院李衛(wèi)明編譯器合成的拷貝構造也稱為淺復制構造,對于采用動態(tài)分配的鏈集合類并不適用。鏈集合類需要重載拷貝構造函數(shù),才能達到正確效果,如圖3.4所示。這樣重載的拷貝構造函數(shù)稱為深復制構造。深復制構造完成后A、B對象相互獨立存在,互不影響。拷貝構造函數(shù)形式如下:CSet(constCSet&rhs);圖3.4鏈集合類重載的拷貝構造效果圖計算機學院李衛(wèi)明定義字符串類復制構造函數(shù)解決動態(tài)內存的問題定義復制構造函數(shù),采用深層復制,通過復制指針數(shù)據(jù)成員strValue所指向的動態(tài)空間中的內容。這樣,兩個對象的指針成員strValue就擁有不同的地址值,指向不同的動態(tài)存儲空間,但兩個動態(tài)空間中的內容完全一樣。計算機學院李衛(wèi)明//定義復制構造函數(shù)避免默認構造函數(shù)的副作用。classString{private:
char*strValue; //串值public:
String(char*s="") //構造函數(shù)
{ if(s==NULL)s=""; //將空指針轉化為空串
strValue=newchar[strlen(s)+1];//分配存儲空間
strcpy(strValue,s); //復制串值
} String(constString©) //復制構造函數(shù)
{ strValue=newchar[strlen(copy.strValue)+1];//分配空間
strcpy(strValue,copy.strValue); //復制串值
} ~String(){delete[]strValue;} //析構函數(shù)
voidShow(){cout<<strValue<<endl;} //顯示串 };intmain() //主函數(shù)main(){ Strings1(“test"); //調用普通構造函數(shù)的生成對象s1 Strings2(s1); //調用復制構造函數(shù)的生成對象s2
s1.Show(); //顯示串s1 s2.Show(); //顯示串s2 ……}程序運行時屏幕輸出如下:testtest請按任意鍵繼續(xù)...計算機學院李衛(wèi)明
使用復制構造的三種情形
一.構造對象時使用同類對象顯式初始化: CSet B;...CSet A=B;或CSetA(B);或CSetA{B};//C++11二.函數(shù)調用參數(shù)調用采用傳值方式時,實參采用拷貝構造方式傳遞給形參voidFun(CSample obj); ...Fun(someObj);三.函數(shù)調用返回值對象時queue<int> GetIntQueue(){ queue<int>inputQueue;.... returninputQueue;}此外,作為對象的成員(子對象)隨宿主對象的復制構造而復制構造計算機學院李衛(wèi)明如用戶沒有為一個類重載賦值運算符,編譯程序將生成一個默認賦值運算符函數(shù),把源對象的數(shù)據(jù)成員逐個賦值給目的對象的相應數(shù)據(jù)成員.對于一般的類,使用默認賦值運算符函數(shù)都能正常地工作,但當一個類中包含有指針類型的數(shù)據(jù)成員,并且通過指針在構造函數(shù)中動態(tài)申請了存儲空間,在析構函數(shù)中通過指針釋放了動態(tài)存儲空間,這種情況可能會出現(xiàn)運行時錯誤;類似于拷貝構造情況。一般地,如果一個類重載了拷貝構造函數(shù),而且使用了=,那么也需要重載=。計算機學院李衛(wèi)明3.2.2 拷貝賦值
圖3.5鏈集合類對象的賦值前狀態(tài)示意圖圖3.6B=A;鏈集合類編譯器合成的拷貝賦值效果圖計算機學院李衛(wèi)明//使用賦值運算符出現(xiàn)運行時錯誤的示例。classString{private:
char*strValue; //串值public:
String(constchar*s="") //構造函數(shù)
{ if(s==NULL)s=""; //將空指針轉化為空串
strValue=newchar[strlen(s)+1]; //分配存儲空間
strcpy(strValue,s); //復制串值
} String(constString©) //復制構造函數(shù)
{ strValue=newchar[strlen(copy.strValue)+1];//分配空間
strcpy(strValue,copy.strValue); //復制串值
} ~String(){delete[]strValue;} //析構函數(shù)
voidShow()const{cout<<strValue<<endl;} //顯示串};……計算機學院李衛(wèi)明intmain() //主函數(shù)main(){ Strings1("try"),s2; //定義對象
s2=s1; //使用默認賦值運算符函數(shù)
s1.Show(); //顯示串s1 s2.Show(); //顯示串s2}程序運行時屏幕可能輸出如下:trytry請按任意鍵繼續(xù)...當用戶按任一鍵時,屏幕將會顯示類似DebugAssertionFailed!的錯誤計算機學院李衛(wèi)明在執(zhí)行“Strings1(“try”);”語句時,構造函數(shù)動態(tài)地分配存儲空間,并將返回的地址賦給對象s1的數(shù)據(jù)成員strValue,然后把“try”拷貝到這塊空間中執(zhí)行語句“s2=s1;”時,由于沒有為類String重載賦值運算符,系統(tǒng)將調用默認賦值運算符函數(shù),負責將對象s1的數(shù)據(jù)成員strValue中存放的地址值賦值給對象s2的數(shù)據(jù)成員strValue對象s1復制給對象s2的僅是其數(shù)據(jù)成員strValue的值,并沒有把strValue指向的動態(tài)存儲空間進行復制,當遇到對象的生命期結束需要撤銷對象時,首先由s2對象調用析構函數(shù),將strValue成員所指向的字符串“try”所在的動態(tài)空間釋放在對象s1自動調用析構函數(shù)之前,對象s1的數(shù)據(jù)成員strValue指向已釋放的內存空間,因此在s1調用析構函數(shù)時,無法正確執(zhí)行析構函數(shù)代碼“delete[]strValue”,從而導致出錯。計算機學院李衛(wèi)明編譯器合成的拷貝賦值運算也稱為淺拷貝賦值,對于采用動態(tài)分配的類并不適用。
鏈集合類需要重載賦值運算符,才能達到正確效果,釋放B集合對象的原鏈表,再將A對象的鏈表復制過來,復制后狀態(tài)如圖3.7所示。這樣重載的賦值運算稱為深拷貝賦值。深拷貝賦值完成后A、B對象相互獨立存在,互不影響??截愘x值運算符重載形式如下:CSet&operator=(constCSet&rhs);圖3.7鏈集合類重載的拷貝賦值效果圖計算機學院李衛(wèi)明字符串類重載賦值運算符對于字符串類,也應重載賦值運算符,復制指針數(shù)據(jù)成員strValue所指向的動態(tài)空間中的內容。這樣,兩個對象的指針成員strValue就擁有不同的地址值,指向不同的動態(tài)存儲空間:計算機學院李衛(wèi)明賦值運算符=重載的一般形式C++規(guī)定賦值運算符=只能重載為類的成員函數(shù),一般重載格式為:類名&類名::operator=(const類名&源對象){ if(this!=&源對象) { //目的對象與源對象不是同一個對象
…… //復制被被賦值對象
} return*this; //返回目的對象}計算機學院李衛(wèi)明//重載賦值運算符避免使用默認賦值運算符的嚴重問題。classString{private:
char*strValue; //串值public:
String(constchar*s="") //構造函數(shù)
{ if(s==NULL)s=""; //將空指針轉化為空串
strValue=newchar[strlen(s)+1];//分配存儲空間
strcpy(strValue,s); //復制串值
} String(constString©) //復制構造函數(shù)
{ strValue=newchar[strlen(copy.strValue)+1];//分配空間
strcpy(strValue,copy.strValue); //復制串值
} String&operator=(constString©); //重載賦值運算符
~String(){delete[]strValue;} //析構函數(shù)
voidShow()const{cout<<strValue<<endl;} //顯示串};……計算機學院李衛(wèi)明String&String::operator=(constString©) //重載賦值運算符{ if(this!=©) {//目的對象與源對象不是同一個對象 delete[]strValue;
strValue=newchar[strlen(copy.strValue)+1];//分配空間
strcpy(strValue,copy.strValue);//復制串值
} return*this; //返回目的對象}intmain() //主函數(shù)main(){ Strings1("try"),s2; //定義對象
s2=s1; //使用重載賦值運算符
s1.Show(); //顯示串s1 s2.Show(); //顯示串s2}程序運行時屏幕輸出如下:trytry請按任意鍵繼續(xù)...計算機學院李衛(wèi)明//重載賦值運算符的另一種好方法,更符合異常安全特性。……String&String::operator=(constString&rhs) //重載賦值運算符{ String copy(rhs); //此時,如果發(fā)生異常,不影響原對象 swap(strValue,copy.strValue);
return*this; //返回目的對象}intmain() //主函數(shù)main(){ Strings1("try"),s2; //定義對象
s2=s1; //使用重載賦值運算符
s1.Show(); //顯示串s1 s2.Show(); //顯示串s2}程序運行時屏幕輸出如下:trytry請按任意鍵繼續(xù)...計算機學院李衛(wèi)明
我們通過重載拷貝構造函數(shù)和拷貝賦值解決了具有動態(tài)分配類的對象的拷貝構造和賦值問題,可保證我們程序的正確執(zhí)行效果。
考慮如下語句:CSetC=A.Union(B);執(zhí)行過程中集合A與集合B進行并運算,結果先保存在一個局部對象中,并運算返回后,編譯器通過重載的拷貝構造將結果局部對象復制給臨時匿名返回值集合對象,局部對象消失,執(zhí)行析構函數(shù),釋放鏈表,最后,再根據(jù)匿名返回值對象復制構造對象C,C得到正確結果,匿名返回值對象消失,執(zhí)行析構函數(shù),釋放鏈表。這個過程結果正確,也沒有造成內存泄漏,但內含若干次不必要的鏈表復制和釋放,造成極大性能浪費,效率極低。計算機學院李衛(wèi)明3.2.3 C++11移動構造
C++11之前主要通過編譯器優(yōu)化解決這個效率問題,但有些情況下,編譯器無法解決這一效率問題,如交換2個鏈集合對象語句:CSettmp=A;A=B;B=tmp;這些語句執(zhí)行過程中存在多次鏈表復制和銷毀,極大影響了執(zhí)行效率。計算機學院李衛(wèi)明C++11引入了移動構造和移動賦值用于解決這一類問題,移動構造也稱轉移構造。
根據(jù)A對象移動構造B對象前,A、B對象狀態(tài)與圖3.2所示拷貝構造時狀態(tài)一樣。A對象事后無需使用,只需簡單的將A對象鏈表轉移給B對象,A對象不再擁有鏈表,A對象指針置nullptr,消失時析構函數(shù)不再釋放鏈表,實現(xiàn)高效完美的資源即鏈表的轉移,這也是轉移構造名稱的由來。圖3.8是轉移后的效果圖。圖3.2鏈集合類的移動構造前效果圖圖3.8鏈集合類的移動構造效果圖計算機學院李衛(wèi)明如果我們用臨時對象給對象賦值,也存在類似情況??紤]如下語句:C=A.Union(B);執(zhí)行過程中集合A與集合B并運算,結果先保存在一個局部對象中,并運算返回后,編譯器先將保存在局部復制一個臨時匿名返回值集合對象,局部對象消失,執(zhí)行析構函數(shù),釋放鏈表,最后,再根據(jù)匿名返回值對象賦值給對象C,C得到正確結果,匿名返回值對象消失,執(zhí)行析構函數(shù),釋放鏈表。這個執(zhí)行過程結果正確,也沒有造成內存泄漏,但內含若干次不必要的鏈表復制、賦值和釋放,造成極大性能浪費,效率極低。同樣,C++98主要通過編譯器優(yōu)化解決這個效率問題,但有些情況下,編譯器無法解決這一效率問題,如前面交換2個鏈集合對象。C++11引入了移動賦值用于解決這一問題,移動賦值也稱轉移賦值。計算機學院李衛(wèi)明3.2.4 C++11移動賦值將對象A移動賦值給B時,A、B對象狀態(tài)與移動構造時不同,與圖3.5所示復制賦值時相同,賦值前對象B已存在,A、B對象事先均擁有鏈表。
圖3.5鏈集合類的移動賦值前效果圖計算機學院李衛(wèi)明如果A對象事后無需使用,只需先釋放B對象的鏈表,再將A對象鏈表轉移給B對象,A對象不再擁有鏈表,A對象指針置nullptr,消失時析構函數(shù)不再釋放鏈表,實現(xiàn)高效完美的資源即鏈表的轉移。圖3.9是這樣方法處理后的移動賦值效果示意圖。
圖3.9鏈集合類的移動賦值效果圖1
計算機學院李衛(wèi)明更好、更簡單的方法。
當前對象廢棄的資源(鏈表)轉移給臨時對象A,將來臨時對象消失時會執(zhí)行析構函數(shù),完成資源的釋放。圖3.10是采用這一處理方法的移動賦值效果示意圖。圖3.10鏈集合類的移動賦值效果圖2計算機學院李衛(wèi)明轉移構造函數(shù)和轉移賦值運算符一般形式如果傳進來的對象是一個臨時對象(馬上就銷毀),我們自然希望能夠繼續(xù)使用這個臨時對象的空間,這樣可以節(jié)省申請空間和復制的時間。C++11支持新移動構造和移動賦值機制,它將臨時對象(馬上就銷毀)資源轉移至新對象,顯著提高執(zhí)行效率。語法形式如下:CSample(CSample&&rhs);CSample&operator=CSample(CSample&&rhs);保證不會拋出異常時,最好聲明:CSample(CSample&&rhs)noexcept;CSample&operator=CSample(CSample&&rhs)noexcept;現(xiàn)代C++STL庫已據(jù)此作修正.noexcept聲明無異常,有利于容器內使用時優(yōu)化性能(如vector擴展空間時,會調用無異常的移動版本來完成搬動,不會調用有異常的移動版本),如有異常,不可聲明noexcept。
本章第4節(jié)鏈集合向量空間擴充探討,說明了需要聲明noexcept的理由。移動構造和移動賦值后必須保證源對象可正常析構,一般應該可以正常重新賦值(內容已變空)。計算機學院李衛(wèi)明#include<iostream>#include<memory>#include<cstring>usingnamespacestd;classString{private: char*strValue; //串值public: String(constchar*s=""); //構造函數(shù) String(constString©); //復制構造函數(shù) String(String&©)noexcept; //移動構造函數(shù) String&operator=(constString©); //重載賦值運算符 String&operator=(String&©)noexcept;//移動賦值 StringReverse()const;
//返回逆序字符串 ~String(){delete[]strValue;} //析構函數(shù) voidShow()const; //顯示串};計算機學院李衛(wèi)明String::String(constchar*s) //構造函數(shù){if(s==NULL)s=""; //將空指針轉化為空串strValue=newchar[strlen(s)+1];//分配存儲空間strcpy(strValue,s); //復制串值}String::String(constString©) //復制構造函數(shù){strValue=newchar[strlen(copy.strValue)+1];//分配空間strcpy(strValue,copy.strValue); //復制串值}String::String(String&©) noexcept //移動構造函數(shù){strValue=copy.strValue;//轉移字符串值copy.strValue=NULL; //已轉移,避免再次delete}計算機學院李衛(wèi)明String&String::operator=(constString©) //重載賦值運算符{ if(this!=©) {//目的對象與源對象不是同一個對象 delete[]strValue; strValue=newchar[strlen(copy.strValue)+1];//分配空間 strcpy(strValue,copy.strValue);//復制串值 } return*this; //返回目的對象}String&String::operator=(String&©)noexcept{ //移動賦值char*t=strValue;strValue=copy.strValue;copy.strValue=t;//交換字符串值return*this;}計算機學院李衛(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;}
計算機學院李衛(wèi)明intmain() //主函數(shù)main(){ Strings1("try"),s2,s3; //定義對象 s2=s1; //使用重載賦值運算符 s3=s1.Reverse();//返回臨時字符串對象使用移動賦值 s1.Show(); //顯示串s1 s2.Show(); //顯示串s2 s3.Show(); //顯示串s2 Strings4(std::move(s1));
//強制使用移動構造,頭文件 //<utility> s1.Show(); //顯示串s1 s4.Show(); //顯示串s4}計算機學院李衛(wèi)明
一般來說,沒有使用動態(tài)分配的類無需定義拷貝控制函數(shù);具有動態(tài)分配的類需要同時定義組成拷貝控制的五個函數(shù):拷貝構造函數(shù)、拷貝賦值運算符、析構函數(shù)、移動構造函數(shù)、移動賦值運算符,拷貝控制函數(shù)中析構函數(shù)用來釋放對象占用的資源;后兩個函數(shù)是C++11新引入的,這個法則就是通常所述的三/五法則。如果類沒有定義自己的拷貝構造函數(shù)、拷貝賦值、移動構造函數(shù)、移動賦值,編譯器會給所有的類提供合成的拷貝構造函數(shù)、拷貝賦值、移動構造函數(shù)、移動賦值。如果類已含有復制構造、復制賦值、移動構造、移動賦值函數(shù)之一,編譯器不會合成其它幾個函數(shù)。當然,如果由于類具有無法拷貝構造、拷貝賦值、移動構造、移動賦值的數(shù)據(jù)成員時,編譯器也無法合成相應函數(shù)。除已刪除相應功能的特殊容器外,C++11標準庫提供的容器都已實現(xiàn)拷貝構造、拷貝賦值、移動構造、移動賦值和析構這五個拷貝控制函數(shù)?,F(xiàn)代C++程序設計需要表示復雜對象狀態(tài)時,應該優(yōu)先使用STL提供的容器作為類成員,這樣就無需使用動態(tài)分配,這樣的類也就無需定義拷貝控制有關函數(shù),直接由編譯器合成即可。計算機學院李衛(wèi)明3.2.5 std::move應用使用類的拷貝控制函數(shù)時,一般由編譯器決定實際使用的是拷貝版還是移動版,針對臨時對象如函數(shù)返回值對象,編譯器使用移動構造或移動賦值,對于具有名字的普通對象,編譯器會使用拷貝構造或拷貝賦值。如果需要對具有名字的普通對象使用移動構造或移動賦值,C++標準庫提供了std::move函數(shù)模板,視指定具名對象為臨時對象,拷貝或賦值時使用移動構造或移動賦值。std::move可適合各類對象,使用方法與普通函數(shù)基本相同,關于函數(shù)模板,詳見第6章。如下述函數(shù)交換2個鏈集合對象時,使用移動構造和移動賦值完成交換,具有非常高的效率,算法時間復雜性為O(1)。voidswapSet(CSet&A,CSet&B){CSettmp=std::move(A);//根據(jù)A移動構造tmpA=std::move(B); //B移動賦值給AB=std::move(tmp); //tmp移動賦值給B}實際無需自己定義上述函數(shù),C++標準庫提供了函數(shù)模板swap??梢灾苯诱{用:swap(A,B);完成對象交換。只要對象的類具有高效率、無異常的移動構造、移動賦值,這樣的交換就是高效、無異常的。計算機學院李衛(wèi)明3.3 典型范例——鏈表表示的集合類實現(xiàn)樣例Ex3.1綜合以上分析,設計實現(xiàn)了功能較完備的集合類,集合內元素采用帶頭結點單鏈表表示,集合元素類型為整形,元素遞增排序。樣例里集合類支持集合顯示和元素增加、查詢,并支持集合并運算,運算結果返回集合對象;樣例設計和實現(xiàn)了拷貝構造和拷貝賦值、移動構造和移動賦值、析構函數(shù)五個拷貝控制函數(shù),不會有內存泄漏。樣例利用該集合類完成了基本測試,測試程序輸入數(shù)據(jù)開始為兩個正整數(shù)m,n;后續(xù)m個整數(shù)構成集合A,再后續(xù)n個整數(shù)構成集合B,輸出集合A、B和他們的并集。限于篇幅,具體樣例代碼和介紹參見附件。計算機學院李衛(wèi)明3.4 *鏈集合向量空間擴充探討STL提供了程序設計中廣泛使用的向量類模板。如果向量元素類型為鏈集合時,STL向量類模板就可以產生鏈集合向量模板類,它的實例就是鏈集合向量,是一種復雜的典型容器對象。鏈集合向量可以存放若干鏈集合對象,并且具有根據(jù)需要調用pushback在尾部添加鏈集合對象的能
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 科技家居新風尚小戶型智能公寓案例分享
- 自然災害下的家庭應急準備
- 家庭教育與未來教育的結合點
- 教育領域的科技創(chuàng)新在線小學數(shù)學輔導策略研究
- 科技賦能企業(yè)安全生產管理新模式探索
- 營養(yǎng)午餐計劃與校園文化的構建
- 教育環(huán)境中加強學生防疫知識教育的措施與方法探討
- 教育展館的互動式學習空間設計
- 2025年漳州衛(wèi)生職業(yè)學院高職單招高職單招英語2016-2024歷年頻考點試題含答案解析
- 小學數(shù)學課堂互動與孩子溝通技巧
- 九年級上冊-備戰(zhàn)2024年中考歷史總復習核心考點與重難點練習(統(tǒng)部編版)
- 健康指南如何正確護理蠶豆病學會這些技巧保持身體健康
- 老客戶的開發(fā)與技巧課件
- 2024建設工程人工材料設備機械數(shù)據(jù)分類和編碼規(guī)范
- 26個英文字母書寫(手寫體)Word版
- GB/T 13813-2023煤礦用金屬材料摩擦火花安全性試驗方法和判定規(guī)則
- DB31 SW-Z 017-2021 上海市排水檢測井圖集
- 日語專八分類詞匯
- GB/T 707-1988熱軋槽鋼尺寸、外形、重量及允許偏差
- GB/T 33084-2016大型合金結構鋼鍛件技術條件
- 高考英語課外積累:Hello,China《你好中國》1-20詞塊摘錄課件
評論
0/150
提交評論