在C++中,有三大函數(shù)復(fù)制控制(復(fù)制構(gòu)造函數(shù),賦值操作符,析構(gòu)函數(shù)),而在C++11中,加入了移動構(gòu)造函數(shù),移動賦值操作符。我就斗膽將他們命名為六大函數(shù)好了。
一、構(gòu)造函數(shù)
C++ Primer中說過:構(gòu)造函數(shù)是特殊的成員函數(shù),只要創(chuàng)建類類型的新對象,都要執(zhí)行構(gòu)造函數(shù)。構(gòu)造函數(shù)的工作就是保證每個對象的數(shù)據(jù)成員具有合適的初始值。
構(gòu)造函數(shù)與其他函數(shù)不同:構(gòu)造函數(shù)和類同名,沒有返回類型。
構(gòu)造函數(shù)與其他函數(shù)相同:構(gòu)造函數(shù)也有形參表(可為void)和函數(shù)體。 ?(參數(shù)表為void的構(gòu)造函數(shù)為默認(rèn)構(gòu)造函數(shù))
構(gòu)造函數(shù)構(gòu)造類對象的順序是:1.內(nèi)存分配,構(gòu)造函數(shù)調(diào)用的時候 隱士\顯示的初始化各數(shù)據(jù)。
2.執(zhí)行構(gòu)造函數(shù)的運行。
1、構(gòu)造函數(shù)初始化表
A() :a(0){}
我們使用構(gòu)造函數(shù)初始化表示初始化數(shù)據(jù)成員,然而在沒有使用初始化表的構(gòu)造函數(shù)則在構(gòu)造函數(shù)體中對數(shù)據(jù)成員賦值。
在我們編寫類的時候,有些成員必須在構(gòu)造函數(shù)初始化表中進(jìn)行初始化。(沒有默認(rèn)構(gòu)造函數(shù)的類類型成員,const或者引用類型成員)
在編寫代碼的時候,要注意的是:可以初始化const對象或者引用類型的對象,但不能對他們進(jìn)行賦值。 也就是需要在我們執(zhí)行構(gòu)造函數(shù)函數(shù)體之前完成初始化工作,所以唯一的機(jī)會就是初始化表。從這一點可以看出初始化表的執(zhí)行先于函數(shù)體。
在初始化表中,成員被初始化的次序不是你編寫初始化表的次序,而是定義成員的次序。
初始化列表在初始化類類型的成員時,要指定實參并傳遞給成員類型的一個構(gòu)造函數(shù)。
在c++primer中有一個書店的例子:
Sales-item(): isbn(10,'9'), units_sold(0), revenue(0.0) {}
我們的初始化表在什么時候必須使用呢 ?
當(dāng)有一個類成員,他本身就是結(jié)構(gòu)或者類的時候,并且只有一個帶參數(shù)的構(gòu)造函數(shù),(無默認(rèn)構(gòu)造函數(shù)) 此時我們要對成員進(jìn)行初始化,就需要調(diào)用成員的構(gòu)造函數(shù),此時需要我們的初始化表,如果不使用初始化表,那么內(nèi)存分配就會出問題。
初始化列表的優(yōu)點:主要是對于自定義類型,初始化列表是作用在函數(shù)體之前,他調(diào)用構(gòu)造函數(shù)對對象進(jìn)行初始化。
然而在函數(shù)體內(nèi),需要先調(diào)用構(gòu)造函數(shù),然后進(jìn)行賦值,這樣效率就不如初始化表。
2、默認(rèn)實參構(gòu)造函數(shù)
A(inti = 1) :a(i), ca(i), ra(i){}
3、默認(rèn)構(gòu)造函數(shù)
合成的默認(rèn)構(gòu)造函數(shù):當(dāng)類中沒有定義構(gòu)造函數(shù)(注意是構(gòu)造函數(shù))的時候,編譯器自動生成的函數(shù)。
但是我們不能過分依賴編譯器,如果我們的類中有復(fù)合類型或者自定義類型成員,我們需要自己定義構(gòu)造函數(shù)。
自定義的默認(rèn)構(gòu)造函數(shù):
A(): a(0) {}
A(inti?=?1):?a(i)?{}
可能疑問的是第二個構(gòu)造函數(shù)也是默認(rèn)構(gòu)造函數(shù)么?是的,因為參數(shù)中帶有默認(rèn)值。
4、類型轉(zhuǎn)換
在C++primer中,書店問題中的一個例子是 傳遞string對象或者iostream對象到參數(shù)中,會發(fā)生隱式轉(zhuǎn)換,這樣就會出現(xiàn)問題。
explicit關(guān)鍵字可以抑制隱式轉(zhuǎn)換。
explicitSales_item(conststring &book): isbn(book) {}
如果我們聲明了構(gòu)造函數(shù)禁止隱式轉(zhuǎn)換, 可以將其他對象顯示轉(zhuǎn)換后傳入構(gòu)造函數(shù)。
string a ="d";
item.same(Sales_item(a));
二、移動構(gòu)造函數(shù)
在C++11中新加入的特性!
通過下圖,可以具體看到移動構(gòu)造函數(shù)的運行原理。

此時,我們偷走了臨時變量的內(nèi)存空間,據(jù)為己用。節(jié)省了開辟空間的時間。
A(A && h) : a(h.a) {
h.a?=?nullptr;//還記得nullptr?
}
可以看到,這個構(gòu)造函數(shù)的參數(shù)不同,有兩個&操作符,? 移動構(gòu)造函數(shù)接收的是“右值引用”的參數(shù)。
還要來說一下,這里h.a置為空,如果不這樣做,h.a在移動構(gòu)造函數(shù)結(jié)束時候執(zhí)行析構(gòu)函數(shù)會將我們偷來的內(nèi)存析構(gòu)掉。h.a會變成懸垂指針。
移動構(gòu)造函數(shù)何時觸發(fā)? ?那就是臨時對象(右值)。用到臨時對象的時候就會執(zhí)行移動語義。
這里要注意的是,異常發(fā)生的情況,要盡量保證移動構(gòu)造函數(shù) 不發(fā)生異常,可以通過noexcept關(guān)鍵字,這里可以保證移動構(gòu)造函數(shù)中拋出來的異常會直接調(diào)用terminate終止程序。
右值引用:
在上一篇blog中,我們提到過將亡值,他是c++11新增的跟右值引用相關(guān)的表達(dá)式。
在c++11中,右值引用就是對一個右值進(jìn)行引用的類型,右值通常不具有名字,我們就只能通過引用的方式找到它的存在了。
比較一下下面兩條語句:
T &&a = returna();
T?b?=?returnb();
此時a是右值引用,他比b少了一次對象析構(gòu)和對象構(gòu)造的過程。a直接綁定了returna返回的臨時變量。b只是由臨時變量值構(gòu)造而成的。
應(yīng)該可以看清楚了吧。右值引用就是讓返回的右值(臨時對象)重獲新生,延長生命周期。臨時對象析構(gòu)了,但是右值引用存活。
不過要注意的是,右值引用不能綁定左值:int a; int &&c = a; ? 這樣是不行的。
這里有一個函數(shù)就是 move函數(shù),它能夠?qū)⒆笾祻?qiáng)制轉(zhuǎn)換成右值引用。
三、移動賦值操作符
他的原理跟移動
構(gòu)造函數(shù)相同,這里不再多說。
給出實現(xiàn)代碼:
A & operator = (A&& h)
{
assert(this!=?&h);
a?=?nullptr;
a?=?move(h.a);
h.a?=?nullptr;
return*this;
}
復(fù)制控制
四、復(fù)制構(gòu)造函數(shù)
他是一種特殊的構(gòu)造函數(shù),具有單個形參,形參是對該類類型的引用。當(dāng)定義一個新對象并用一個同類型的對象對它進(jìn)行初始化時,將顯式使用復(fù)制構(gòu)造函數(shù)。當(dāng)將該類型的對象傳遞給函數(shù)或從函數(shù)返回該類型的對象時,將隱式使用復(fù)制構(gòu)造函數(shù)。
必須定義復(fù)制構(gòu)造函數(shù)的情況:
1.、類有一個或者多個數(shù)據(jù)成員是指針。
2、有成員表示在構(gòu)造函數(shù)中分配的其他資源。另外的類在創(chuàng)建新對象時必須做一些特定的工作。
下面給出賦值構(gòu)造函數(shù)的編寫:
A(constA& h) :a(h.a){}
如果不想讓對象復(fù)制呢? 那就將復(fù)制構(gòu)造函數(shù)聲明為:private;
五、賦值操作符
他跟構(gòu)造函數(shù)一樣,賦值操作符可以通過制定不同類型的右操作數(shù)而重載。
賦值和復(fù)制經(jīng)常是一起使用的,這個要注意。
下面給出賦值操作符的寫法:
五、賦值操作符
他跟構(gòu)造函數(shù)一樣,賦值操作符可以通過制定不同類型的右操作數(shù)而重載。
賦值和復(fù)制經(jīng)常是一起使用的,這個要注意。
下面給出賦值操作符的寫法:
A& operator = (constA& h)
{
assert(this!=?&h);
this->a?=?h.a;
return*this;
}
六、析構(gòu)函數(shù)
是構(gòu)造函數(shù)的互補(bǔ),當(dāng)對象超出作用域或動態(tài)分配的對象被刪除時,將自動應(yīng)用析構(gòu)函數(shù)。析構(gòu)函數(shù)可用于釋放對象時構(gòu)造或在對象的生命期中所獲取的資源。不管類是否定義了自己的析構(gòu)函數(shù),編譯器都會自動執(zhí)行類中非static數(shù)據(jù)成員的析構(gòu)函數(shù)。
析構(gòu)函數(shù)的運行:
當(dāng)對象引用或指針越界的時候不會執(zhí)行析構(gòu)函數(shù),只有在刪除指向動態(tài)分配對象的指針或?qū)嶋H對象超出作用域時才會調(diào)用析構(gòu)函數(shù)。
合成析構(gòu)函數(shù):
編譯器總是會合成一個析構(gòu)函數(shù),合成析構(gòu)函數(shù)按對象創(chuàng)建時的逆序撤銷每個非static成員。要注意的是,合成的析構(gòu)函數(shù)不會刪除指針成員所指向的對象。
最后要注意的是:類如果需要析構(gòu)函數(shù),那么他肯定也需要復(fù)制構(gòu)造函數(shù)和賦值操作符。
blog的最后給出完整的六大函數(shù)的代碼。