C++類的默認函數

  在C++中,一個類有八個默認函數:html

  1. 默認構造函數;
  2. 默認拷貝構造函數;
  3. 默認析構函數;
  4. 默認重載賦值運算符函數;
  5. 默認重載取址運算符函數;
  6. 默認重載取址運算符const函數;
  7. 默認移動構造函數(C++11);
  8. 默認重載移動賦值操做符函數(C++11)。

    只是聲明一個空類,不作任何事情的話,編譯器會自動爲你生成一個默認構造函數、一個默認拷貝構造函數、一個默認重載賦值操做符函數和一個默認析構函數。這些函數只有在第一次被調用時,纔會被編譯器建立,固然這幾個生成的默認函數的實現就是什麼都不作。全部這些函數都是inline和public的。ide

  咱們不但願對象被顯示構造(單列模式)或賦值,能夠將對應函數聲明爲private,或者寫一個基類,開放部分默認函數,子類去繼承就能夠了。C++11新增標識符default和delete,控制這些默認函數是否使用。函數

  default:被標識的默認函數將使用類的默認行爲,如:A() = default;性能

  delete:被標識的默認函數將禁用,如:A() = delete;
學習

    override:被標識的函數須要強制重寫基類虛函數;大數據

    final:被標識的函數禁止重寫基類虛函數;this

 1 class A
 2 {
 3 public:
 4 
 5     // 默認構造函數;
 6     A();
 7 
 8     // 默認拷貝構造函數
 9     A(const A&);
10 
11     // 默認析構函數
12     ~A();
13 
14     // 默認重載賦值運算符函數
15     A& operator = (const A&);
16 
17     // 默認重載取址運算符函數
18     A* operator & ();
19 
20     // 默認重載取址運算符const函數
21     const A* operator & () const;
22 
23     // 默認移動構造函數
24     A(A&&);
25 
26     // 默認重載移動賦值操做符
27     A& operator = (const A&&);
28 
29 };

  下面就每一個函數分別詳細討論學習,有不對之處,歡迎評論指正,您的批評指正就是在下前進的不竭動力!人工智能

1、構造函數(Constructor)

1.構造函數做用是對對象進行初始化,在堆上new一個對象或在棧上定義一個臨時對象時,會自動調用對象的構造函數。有初始化列表構造函數體內賦值兩種方式,初始化列表在初始化對象時更高效(每一個成員在初始化列表中只能出現一次),減小了一次賦值操做,推薦此方法;如下成員變量必須在初始化列表中初始化:常量成員變量引用類型成員變量沒有缺省構造函數的成員變量(若是構造函數的參數列表中有一個類的對象,而且該對象的類裏沒有缺省參數的構造函數時,要是不使用初始化列表,參數中會調用無參或者全缺省的構造函數,而那個類中又沒有);spa

2.函數名與類名相同,能夠重載,不能爲虛函數,不能有返回值,連void也不行;設計

3.若是沒有顯式定義,編譯器會自動生成一個默認的構造函數,默認的構造函什麼都不會作;

4.無參構造函數和帶有缺省值的構造函數(全缺省)都認爲是缺省的構造函數,而且缺省的構造函數只能有一個;

5.函數體內可使用this指針,但不能夠用於初始化列表。由於構造函數只是初始化對象,初始化以前此對象已經存在了,因此能夠有this,函數體裏面是進行賦值,初始化列表是對類中的各個成員變量進行初始化,初始化的位置對象不完整,因此不能使用this用於初始化列表;

6.對於出現單參數的構造函數須要注意,C++會默認將參數對應的類型轉換爲該類類型,有時候這種隱式的轉換是咱們不想要的,須要使用explicit關鍵字來限制這種轉換;

7.構造順序:虛擬基類的構造函數(若是有多個虛擬基類,按照它們被繼承的順序構造,而不是它們在成員初始化列表中的順序);

             非虛擬基類的構造函函(若是有多個非虛擬基類,按照它們被繼承的順序構造,而不是它們在成員初始化列表中的順序);
             成員對象的構造函數(若是有多個成員類對象,按照它們聲明的順序調用,而不是它們在成員初始化列表中的順序);
             本類構造函數。構造的過程是遞歸的。

2、拷貝構造函數(Copy Constructor)

1.拷貝構造函數其實是構造函數的重載,具備通常構造函數的全部特性,用此類已有的對象建立一個新的對象,通常在函數中會將已存在對象的數據成員的值複製一份到新建立的對象中。用類的一個已知的對象去初始化該類的另外一個對象時,會自動調用對象的拷貝構造函數;

2.函數名與類名相同,第一個參數是對某個同類對象的引用,且沒有其餘參數或其餘參數都有默認值,返回值是類對象的引用,經過返回引用值能夠實現連續構造,即相似A(B(C))這樣;

3.若是沒有顯式定義,編譯器會自動生成一個默認的拷貝構造函數,默認的拷貝構造函數會依次拷貝類的數據成員完成初始化;

4.淺拷貝和深拷貝:編譯器建立的默認拷貝構造函數只會執行"淺拷貝",也就是經過賦值完成,若是該類的數據成員中有指針成員,也只是地址的拷貝,會使得新的對象與拷貝對象該指針成員指向的地址相同,delete該指針時則會致使兩次重複delete而出錯,若是指針成員是new出來就是「深拷貝」。

3、析構函數(Destructor)

1.析構函數做用是作一些清理工做,delete一個對象或對象生命週期結束時,會自動調用對象的析構函數;

2.函數名在類名前加上字符~,沒有參數(能夠有void類型的參數),也沒有返回值,能夠爲虛函數(經過基類的指針去析構子類對象時候),不能重載,故析構函數只有一個;

3.若是沒有顯式定義,編譯器會自動生成一個默認的析構函數,默認的析構函什麼都不會作;

4.析構順序:和構造函數順序相反。析構的過程也是遞歸的。

4、重載賦值運算符函數(Copy Assignment operator)

1.它是兩個已有對象,一個給另外一個賦值的過程。當兩個對象之間進行賦值時,會自動調用重載賦值運算符函數,它不一樣於拷貝構造函數,拷貝構造函數是用已有對象給新生成的對象賦初值的過程;

2.賦值運算符重載函數參數中const和&沒有強制要求,返回值是類對象的引用,經過返回引用值能夠實現連續賦值,即相似a=b=c這樣,返回值類型也不是強制的,能夠返回void,使用時就不能連續賦值;

3.賦值運算符重載函只能定義爲類的成員函數,不能是靜態成員函數,也不能是友元函數,賦值運算符重載函數不能被繼承,要避免自賦值;

4.若是沒有顯式定義,編譯器會自動生成一個默認的賦值運算符重載函數,默認的賦值運算符重載函數實現將數據成員逐一賦值的一種淺拷貝,會致使指針懸掛問題。

5、重載取址運算符(const)函數

1.重載取址運算符函數沒有參數;

2.若是沒有顯式定義,編譯器會自動生成默認的重載取址運算符函數,函數內部直接return this,通常使用默認便可。

6、移動構造函數和重載移動賦值操做符函數

1.C++11 新增move語義:源對象資源的控制權所有交給目標對象,能夠將原對象移動到新對象, 用於a初始化b後,就將a析構的狀況;

2.移動構造函數的參數和拷貝構造函數不一樣,拷貝構造函數的參數是一個左值引用,可是移動構造函數的初值是一個右值引用;

3.臨時對象即將消亡,而且它裏面的資源是須要被再利用的,這個時候就可使用移動構造。移動構造能夠減小沒必要要的複製,帶來性能上的提高。

7、討論

1.構造函數爲何不能有返回值?

  (1).C++語言規定構造函數沒有返回值;

  (2).構造函數不做爲右值使用,返回值也沒有用;

  (3).就算有返回值,從基本語義角度來說,也應該返回的是所構造的對象,因此不必畫蛇添足來指定返回類型了;

  (4).假若有返回值,討論下面代碼

class A
{
public:
A():m_iTest(0) { }
A(int i):m_iTest(i) { }
private:
int m_iTest;
};

  按照C++的規定,A a = A();是用默認構造函數建立一個臨時對象,並用這個臨時對象初始化a,此時,a.m_iTest的值應該是0。如今若是A::A()有返回值,而且返回了1(表示構形成功),則C++會用1去初始化a,即調用有參數構造函數A::A(int i),獲得的a.m_iTest便會是1。因而,語義產生了歧義,使得C++本來已經很是複雜的語法,進一步混亂不堪。

  構造函數的調用之因此不設返回值,是由於構造函數的特殊性決定的。固然,上面的討論,也是基於C++語言規定,若是規定構造函數能夠有返回值,上面用法也許就不同了。是先有雞仍是先有蛋,這是一個神奇的問題。總之,如今C++語法體系是這樣的,若是設計構造函數能夠有返回值,可能整個C++語言更難實現了。

2.對象建立和銷燬過程是怎樣的?

  對象建立(new)過程:

  (1).經過operator new申請內存;
  (2).使用placement new調用構造函數(簡單類型忽略此步);
  (3).返回內存指針。

  new和malloc的比較:

  (1).new失敗時會調用new_handler處理函數,malloc不會,失敗時返回NULL;
  (2).new能經過placement new自動調用對象的構造函數,malloc不會;
  (3).new出來的東西是帶類型的,malloc是void*,須要強制轉換;
  (4).new是C++運算符,malloc是C標準庫函數。

  new的三種形態:new operator,operator new,placement new

  (1).new operator:上面所說的new就是new operator,共有三個步驟組成(申請內存,調用構造函數,返回內存指針),對於申請內存步驟是經過運算符new(operator new)完成的,對於調用什麼構造函數,能夠由placement new決定;

  (2).operator new:像普通運算符同樣能夠被重載,operator new會去申請內存,申請失敗的時候會調用new_handler處理,這是一個循環的過程,若是new_handler不拋出異常,會一直循環申請內存,直到成功;

  (3).placement new:用於定位構造函數,在指定的內存地址上用指定類型的構造函數構造對象。

  對象銷燬(delete)過程:

  (1).調用析構函數(簡單類型忽略此步);

  (2).釋放內存。

  delete和free比較

  (1).delete能自動調用對象的析構函數,free不會;
  (2).delete是C++運算符,free是C標準庫函數。

3.拷貝構造函數參數爲何必須使用類類型對象引用傳遞?

 傳參的位置若是一直調用拷貝構造函數,也就是會遞歸引用,致使棧溢出。

4.賦值運算符重載函數爲何要避免自賦值?

 (1).提升效率。自賦值無心義,若是自賦值,能夠當即return *this;

   (2).若是不避免,當類的數據成員中若是含有指針,自賦值時會形成內存泄漏。

 

做者:KeepHopes
出處:http://www.javashuo.com/article/p-kggbtvcz-eh.html
關於做者:專一C++,對大數據、人工智能領域頗感興趣,請多多賜教!
本文爲做者原創,版權歸做者和博客園共有,轉載或引用請註明出處,謝謝!
相關文章
相關標籤/搜索