c++中的構造函數和析構函數

(一):構造函數、析構函數、賦值與初始化、explicit關鍵字                     

構造函數是爲了保證對象的每個數據成員都被正確初始化

通常情況下構造函數應聲明爲公有函數,一般被隱式地調用。
構造函數被聲明爲私有有特殊的用途,比如單例模式,以後詳談。
構造函數可以有任意類型和任意個數的參數,一個類可以有多個構造

默認構造函數

不帶參數的構造函數
如果程序中未聲明,則系統自動產生出一個默認構造函數,是空函數

如果程序實現任何一個構造函數(包括拷貝構造函數),那麼編譯器將不再提供默認構造函數

棧上的對象生存期到了會自動調用析構函數;而new operator做了兩件事,一個是創建了對象內存,一個是調用構造函數;堆上的內存需要delete釋放,做了兩件事,一是調用析構函數,二是釋放內存

//我們不能調用一個構造函數但沒有提供參數(實例化對象),如
A a();
//
因爲是有歧義的,我們也可以看成是聲明瞭一個沒有參數的函數a,返回值是類型A的一個對象

但在函數傳參的時候往往可以這樣寫: A()  //即定義一個無名對象。

還有一個注意點,全局對象的構造先於main函數執行,在return 0時全局變量的生存期也到了,故也會自動調用

析構函數

沒有參數

析構函數不能被重載,如果沒有定義析構函數,編譯器會自動生成一個默認析構函數,其格式如下:
類名::~默認析構函數名( )
{
}
默認析構函數是一個空函數

實際上,構造函數和析構函數都是可以被顯式調用的,只是很少這樣做,

new的時候,其實做了兩件事,

一是:調用malloc分配所需內存(實際上是調用operator new),二是:調用構造函數。

delete
的時候,也是做了兩件事,

一是:調用析造函數,二是:調用free釋放內存(實際上是調用operator delete)。

這裏只是爲了演示,正常情況下析構函數只會被調用一次,如果被調用兩次,而析構函數內有delete的操作,會導致內存釋放兩次的錯誤

placement new的作用就是:創建對象(調用該類的構造函數)但是不分配內存,而是在已有的內存塊上面創建對象。用於需要反覆創建並刪除的對象上,可以降低分配釋放內存的性能消耗。

轉換構造函數

單個參數的構造函數不一定是轉換構造函數
將其它類型轉換爲類類型
類的構造函數只有一個參數是非常危險的,因爲編譯器可以使用這種構造函數把參數的類型隱式轉換爲類類型

賦值與初始化的區別

在初始化語句中的等號不是運算符。

#include "Test.h"

int main(void)
{
    Test t = 10;        // 等價於Test t(10); 這裏的=不是運算符,表示初始化。

    t = 20;             // 賦值操作

    Test t2;
    t = t2;             // 賦值操作 t.operator=(t2);


    return 0;
}

 

 

 

 

 

explicit 關鍵字

只提供給類的構造函數使用的關鍵字。
編譯器不會把聲明爲explicit的構造函數用於隱式轉換,它只能在程序代碼中顯示創建對象

(二):初始化列表(const和引用成員)、拷貝構造函數

一、構造函數初始化列表

推薦在構造函數初始化列表中進行初始化
構造函數的執行分爲兩個階段

初始化段

普通計算段

一是構造對象之前,必須先構造對象的成員;二是對象成員構造的順序與定義時的順序有關,跟初始化列表順序無關;三是構造的順序和析構的順序相反;四是如果對象成員對應的類沒有默認構造函數,那對象成員也只能在初始化列表進行初始化。再提一點,如果類是繼承而來,基類沒有默認構造函數的時候,基類的構造函數要在派生類構造函數初始化列表中調用

(二)、const成員、引用成員的初始化

//const成員的初始化只能在構造函數初始化列表中進行
// 引用成員的初始化也只能在構造函數初始化列表中進行
// 對象成員(對象成員所對應的類沒有默認構造函數)的初始化,也只能在構造函數初始化列表中進行

因爲const變量或者引用都得在定義的時候初始化,所以const成員和引用成員必須在初始化列表中初始化。另外,可以使用定義枚舉類型來得到類作用域共有的常量。

如果類中沒有定義拷貝構造函數,則系統自動生成一個缺省複製構造函數,作爲該類的公有成員,所做的事情也是簡單的成員複製

(二)、拷貝構造函數調用的幾種情況

當函數的形參是類的對象,調用函數時,進行形參與實參結合時使用。這時要在內存新建立一個局部對象,並把實參拷貝到新的對象中。理所當然也調

用拷貝構造函數。還有一點,爲什麼拷貝構造函數的參數需要是引用? 這是因爲如果拷貝構造函數中的參數不是一個引用,即形如CClass(const 

CClass c_class),那麼就相當於採用了傳值的方式(pass-by-value),而傳值的方式會調用該類的拷貝構造函數,從而造成無窮遞歸地調用拷貝構造函

數。


當函數的返回值是類對象,函數執行完成返回調用者時使用。也是要建立一個臨時對象,再返回調用者。爲什麼不直接用要返回的局部對象呢?

因爲局部對象在離開建立它的函數時就消亡了,不可能在返回調用函數後繼續生存,所以在處理這種情況時,編譯系統會在調用函數的表達式中創建一

個無名臨時對象,該臨時對象的生存週期只在函數調用處的表達式中。所謂return對象,實際上是調用拷貝構造函數把該對象的值拷入臨時對象。如果

返回的是變量,處理過程類似,只是不調用構造函數

(三):深拷貝與淺拷貝、空類與空數組 

說得簡單點,假設一個類有指針成員,如果在拷貝的時候順帶連指針指向的內存也分配了,就稱爲深拷貝

如果只是分配指針本身的內存,那就是淺拷貝

淺拷貝造成的問題是有兩個指針指向同塊內存,delete其中一個指針,那麼剩下的指針將成爲野指針。編譯器合成的默認拷貝構造函數和賦值運算符是淺拷貝的,如果只是普通成員的賦值,淺拷貝也是可以的。

此外,如果我們想讓對象是獨一無二的,需要禁止拷貝,只需要將拷貝構造函數和等號運算符聲明爲私有,並且不提供它們的實現

注意:在編寫派生類的賦值函數時,不要忘記對基類的數據成員重新賦值,可以通過調用基類的賦值函數來實現

二、空類與空數組

空類默認產生的成員:

class Empty {};
Empty(); //
默認構造函數
Empty( const Empty& ); // 默認拷貝構造函數
~Empty(); // 默認析構函數
Empty& operator=( const Empty& );  // 默認賦值運算符
Empty* operator&();               //
取址運算符
const Empty* operator&() const;    //
取址運算符 const

空類的大小爲1個字節

#include<iostream>
using namespace std;
int main()
{
    
int a[0];
    
class B {};
    
struct C
    {
        
int  m;
        
int  n;
        
char buffer[];
    };
    
class D
    {
        
int  s[0];
    };

    cout << 
"sizeof(a)=" << sizeof(a) << endl; //0
    cout << 
"B{}=" << sizeof(B) << endl; //1
    cout << 
"C=" << sizeof(C) << endl; //8
    cout << 
"D=" << sizeof(D) << endl; //0

    
return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章