C++11六大函數(構造函數,移動構造函數,移動賦值操作符,復制構造函數,賦值操作符,析構函數)

在C++中,有三大函數復制控制(復制構造函數,賦值操作符,析構函數),而在C++11中,加入了移動構造函數,移動賦值操作符。我就斗膽將他們命名為六大函數好了。

一、構造函數

C++ Primer中說過:構造函數是特殊的成員函數,只要創建類類型的新對象,都要執行構造函數。構造函數的工作就是保證每個對象的數據成員具有合適的初始值。

構造函數與其他函數不同:構造函數和類同名,沒有返回類型。

構造函數與其他函數相同:構造函數也有形參表(可為void)和函數體。 ?(參數表為void的構造函數為默認構造函數)

構造函數構造類對象的順序是:1.內存分配,構造函數調用的時候 隱士\顯示的初始化各數據。

2.執行構造函數的運行。

1、構造函數初始化表

A() :a(0){}

我們使用構造函數初始化表示初始化數據成員,然而在沒有使用初始化表的構造函數則在構造函數體中對數據成員賦值。

在我們編寫類的時候,有些成員必須在構造函數初始化表中進行初始化。(沒有默認構造函數的類類型成員,const或者引用類型成員)

在編寫代碼的時候,要注意的是:可以初始化const對象或者引用類型的對象,但不能對他們進行賦值。 也就是需要在我們執行構造函數函數體之前完成初始化工作,所以唯一的機會就是初始化表。從這一點可以看出初始化表的執行先于函數體。

在初始化表中,成員被初始化的次序不是你編寫初始化表的次序,而是定義成員的次序。

初始化列表在初始化類類型的成員時,要指定實參并傳遞給成員類型的一個構造函數。

在c++primer中有一個書店的例子:

Sales-item(): isbn(10,'9'), units_sold(0), revenue(0.0) {}

我們的初始化表在什么時候必須使用呢 ?

當有一個類成員,他本身就是結構或者類的時候,并且只有一個帶參數的構造函數,(無默認構造函數) 此時我們要對成員進行初始化,就需要調用成員的構造函數,此時需要我們的初始化表,如果不使用初始化表,那么內存分配就會出問題。

初始化列表的優點:主要是對于自定義類型,初始化列表是作用在函數體之前,他調用構造函數對對象進行初始化。

然而在函數體內,需要先調用構造函數,然后進行賦值,這樣效率就不如初始化表。

2、默認實參構造函數

A(inti = 1) :a(i), ca(i), ra(i){}

3、默認構造函數

合成的默認構造函數:當類中沒有定義構造函數(注意是構造函數)的時候,編譯器自動生成的函數。

但是我們不能過分依賴編譯器,如果我們的類中有復合類型或者自定義類型成員,我們需要自己定義構造函數。

自定義的默認構造函數:

A(): a(0) {}

A(inti?=?1):?a(i)?{}

可能疑問的是第二個構造函數也是默認構造函數么?是的,因為參數中帶有默認值。

4、類型轉換

在C++primer中,書店問題中的一個例子是 傳遞string對象或者iostream對象到參數中,會發生隱式轉換,這樣就會出現問題。

explicit關鍵字可以抑制隱式轉換。

explicitSales_item(conststring &book): isbn(book) {}

如果我們聲明了構造函數禁止隱式轉換, 可以將其他對象顯示轉換后傳入構造函數。

string a ="d";

item.same(Sales_item(a));

二、移動構造函數

在C++11中新加入的特性!

通過下圖,可以具體看到移動構造函數的運行原理。

此時,我們偷走了臨時變量的內存空間,據為己用。節省了開辟空間的時間。

A(A && h) : a(h.a) {

h.a?=?nullptr;//還記得nullptr?

}

可以看到,這個構造函數的參數不同,有兩個&操作符,? 移動構造函數接收的是“右值引用”的參數。

還要來說一下,這里h.a置為空,如果不這樣做,h.a在移動構造函數結束時候執行析構函數會將我們偷來的內存析構掉。h.a會變成懸垂指針。

移動構造函數何時觸發? ?那就是臨時對象(右值)。用到臨時對象的時候就會執行移動語義。

這里要注意的是,異常發生的情況,要盡量保證移動構造函數 不發生異常,可以通過noexcept關鍵字,這里可以保證移動構造函數中拋出來的異常會直接調用terminate終止程序。

右值引用:

在上一篇blog中,我們提到過將亡值,他是c++11新增的跟右值引用相關的表達式。

在c++11中,右值引用就是對一個右值進行引用的類型,右值通常不具有名字,我們就只能通過引用的方式找到它的存在了。

比較一下下面兩條語句:

T &&a = returna();

T?b?=?returnb();

此時a是右值引用,他比b少了一次對象析構和對象構造的過程。a直接綁定了returna返回的臨時變量。b只是由臨時變量值構造而成的。

應該可以看清楚了吧。右值引用就是讓返回的右值(臨時對象)重獲新生,延長生命周期。臨時對象析構了,但是右值引用存活。

不過要注意的是,右值引用不能綁定左值:int a; int &&c = a; ? 這樣是不行的。

這里有一個函數就是 move函數,它能夠將左值強制轉換成右值引用。

三、移動賦值操作符

他的原理跟移動

構造函數相同,這里不再多說。

給出實現代碼:

A & operator = (A&& h)

{

assert(this!=?&h);

a?=?nullptr;

a?=?move(h.a);

h.a?=?nullptr;

return*this;

}

復制控制

四、復制構造函數

他是一種特殊的構造函數,具有單個形參,形參是對該類類型的引用。當定義一個新對象并用一個同類型的對象對它進行初始化時,將顯式使用復制構造函數。當將該類型的對象傳遞給函數或從函數返回該類型的對象時,將隱式使用復制構造函數。

必須定義復制構造函數的情況:

1.、類有一個或者多個數據成員是指針。

2、有成員表示在構造函數中分配的其他資源。另外的類在創建新對象時必須做一些特定的工作。

下面給出賦值構造函數的編寫:

A(constA& h) :a(h.a){}

如果不想讓對象復制呢? 那就將復制構造函數聲明為:private;

五、賦值操作符

他跟構造函數一樣,賦值操作符可以通過制定不同類型的右操作數而重載。

賦值和復制經常是一起使用的,這個要注意。

下面給出賦值操作符的寫法:

五、賦值操作符

他跟構造函數一樣,賦值操作符可以通過制定不同類型的右操作數而重載。

賦值和復制經常是一起使用的,這個要注意。

下面給出賦值操作符的寫法:

A& operator = (constA& h)

{

assert(this!=?&h);

this->a?=?h.a;

return*this;

}

六、析構函數

是構造函數的互補,當對象超出作用域或動態分配的對象被刪除時,將自動應用析構函數。析構函數可用于釋放對象時構造或在對象的生命期中所獲取的資源。不管類是否定義了自己的析構函數,編譯器都會自動執行類中非static數據成員的析構函數。

析構函數的運行:

當對象引用或指針越界的時候不會執行析構函數,只有在刪除指向動態分配對象的指針或實際對象超出作用域時才會調用析構函數。

合成析構函數:

編譯器總是會合成一個析構函數,合成析構函數按對象創建時的逆序撤銷每個非static成員。要注意的是,合成的析構函數不會刪除指針成員所指向的對象。

最后要注意的是:類如果需要析構函數,那么他肯定也需要復制構造函數和賦值操作符。

blog的最后給出完整的六大函數的代碼。

本文詳情于本人舊博客

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容