標籤:book 關鍵字 move 參數表 命名 資料 出現 自己的 其他
在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(int i = 1) :a(i), ca(i), ra(i){}
3、預設建構函式
合成的預設建構函式:當類中沒有定義建構函式(注意是建構函式)的時候,編譯器自動產生的函數。
但是我們不能過分依賴編譯器,如果我們的類中有複合類型或者自訂類型成員,我們需要自己定義建構函式。
自訂的預設建構函式:
- A(): a(0) {}
- A(int i = 1): a(i) {}
可能疑問的是第二個建構函式也是預設建構函式嗎?是的,因為參數中帶有預設值。
我們來看一張圖,就會一目瞭然了:
4、類型轉換
在C++primer中,書店問題中的一個例子是 傳遞string對象或者iostream對象到參數中,會發生隱式轉換,這樣就會出現問題。
explicit關鍵字可以抑制隱式轉換。
- explicit Sales_item(const string &book): isbn(book) {}
如果我們聲明了建構函式禁止隱式轉換, 可以將其他對象顯示轉換後傳入建構函式。
- string a = "d";
- item.same(Sales_item(a));
二、移動建構函式
在C++11中新加入的特性!
在上一篇blog中我加入了一張圖,可以具體看到移動建構函式的運行原理。
此時,我們偷走了臨時變數的記憶體空間,據為己用。節省了開闢空間的時間。
- 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(const A& h) :a(h.a){}
如果不想讓對象複製呢? 那就將複製建構函式聲明為:private;
五、賦值操作符
他跟建構函式一樣,賦值操作符可以通過制定不同類型的右運算元而重載。
賦值和複製經常是一起使用的,這個要注意。
下面給出賦值操作符的寫法:
- A& operator = (const A& h)
- {
- assert(this != &h);
-
- this->a = h.a;
-
- return *this;
- }
六、解構函式
是建構函式的互補,當對象超出範圍或動態分配的對象被刪除時,將自動應用解構函式。解構函式可用於釋放對象時構造或在對象的生命期中所擷取的資源。不管類是否定義了自己的解構函式,編譯器都會自動執行類中非static資料成員的解構函式。
解構函式的運行:
當對象引用或指標越界的時候不會執行解構函式,只有在刪除指向動態指派至的指標或實際對象超出範圍時才會調用解構函式。
合成解構函式:
編譯器總是會合成一個解構函式,合成解構函式按對象建立時的逆序撤銷每個非static成員。要注意的是,合成的解構函式不會刪除指標成員所指向的對象。
最後要注意的是:類如果需要解構函式,那麼他肯定也需要複製建構函式和賦值操作符。
blog的最後給出完整的六大函數的代碼。
- #include <iostream>
- #include <assert.h>
- using namespace std;
-
- class Temp
- {
- public:
- Temp(const char* str = nullptr);
- Temp(Temp&& t);
- Temp& operator = (Temp&& t);
- Temp(const Temp& t);
- Temp& operator = (Temp& t);
- ~Temp(void);
- private:
- char *m_pData;
- };
-
- Temp::Temp(const char* str)
- {
- if (!str)
- {
- m_pData = nullptr;
- }
- else
- {
- this->m_pData = new char[strlen(str) + 1];
- strcpy(this->m_pData, str);
- }
- }
-
- Temp::Temp(Temp&& t) :m_pData(move(t.m_pData))
- {
- t.m_pData = nullptr;
- }
-
- Temp& Temp::operator = (Temp&& t)
- {
- assert(this != &t);
-
- this->m_pData = nullptr;
- this->m_pData = move(t.m_pData);
- t.m_pData = nullptr;
-
- return *this;
-
- }
-
- Temp::Temp(const Temp& t)
- {
- if (!t.m_pData)
- {
- this->m_pData = nullptr;
- }
- else
- {
- this->m_pData = new char[strlen(t.m_pData) + 1];
- strcpy(this->m_pData, t.m_pData);
- }
- }
-
- Temp& Temp::operator = (Temp &t)
- {
- if (this != &t)
- {
- delete[] this->m_pData;
- if (!t.m_pData)
- {
- this->m_pData = nullptr;
- }
- else
- {
- this->m_pData = new char[strlen(t.m_pData) + 1];
- strcpy(this->m_pData, t.m_pData);
- }
- }
-
- return *this;
- }
-
- Temp::~Temp(void)
- {
- if (this->m_pData)
- {
- delete[] this->m_pData;
- this->m_pData = nullptr;
- }
- }
C++11六大函數(建構函式,移動建構函式,移動賦值操作符,複製建構函式,賦值操作符,解構函式)