【C++】 55_經典問題分析 四

關於動態內存分配

new 和 malloc 的區別是什麼?
delete 和 free 的區別是什麼?ios

  • new 關鍵字和 malloc 函數的區別編程

    • new 關鍵字是 C++ 的一部分
    • malloc 是 C 庫提供的函數
    • new 以具體類型爲單位進行內存分配
    • malloc 以字節爲單位進行內存分配
    • new 在申請內存空間時可進行初始化
    • malloc 僅根據須要申請定量的內存空間

編程實驗: new、delete 與 malloc、free

#include <iostream>
#include <cstdlib>

using namespace std;

class Test
{
private:
    int* mp;
public:
    Test()
    {
        cout << "Test::Test()" << endl;
        
        mp = new int(100);
    }
    ~Test()
    {
        delete mp;
        
        cout << "~Test::Test()" << endl;
    }
};

int main()
{
    Test* pn = new Test;
    Test* pm = (Test*)malloc(sizeof(Test));

    delete pn;
    free (pm);

    return 0;
}
輸出:
Test::Test()
~Test::Test()

分析:

new Test;                    ==> 在堆上建立一個對象,構造函數被調用
(Test*)malloc(sizeof(Test)); ==> 在堆上申請 sizeof(Test) 大小內存,構造函數未被調用,對象未正常建立

delete pn; ==> 銷燬對象, 歸還內存,析構函數被調用
free(pm);  ==> 僅歸還內存,析構函數未被調用

當 delete 與 free 混用,會發生什麼呢?安全

delete --> free :函數

int main()
{
    Test* pn = new Test;

    free (pn);          // 注意這裏!

    return 0;
}
輸出:
Test::Test()

分析:
free 能夠釋放 new 申請的堆空間,但析構函數未被調用,對象未正常銷燬(實例中,致使系統資源泄漏!!)

free --> delete :spa

int main()
{
    Test* pm = (Test*)malloc(sizeof(Test));
      
    delete pm;         // 注意這裏!

    return 0;
}
輸出:
~Test::Test()

分析:
delete 能夠釋放 malloc 申請的堆空間,不合法對象的析構函數被調用!運行結果將是不肯定的!(示例中,將delete一個野指針指向的內存空間)

結論: C++ 中杜絕 malloc、 free 的使用設計

  • new 和 malloc 的區別指針

    • new 在全部 C++ 編譯器中都被支持
    • malloc 在某些系統開發中不能調用
    • new 可以觸發構造函數的調用
    • malloc 僅分配須要的內存空間
    • 對象的建立只能使用 new
    • malloc 不適合面向對象開發

  • delete 和 free 的區別code

    • delete 在全部 C++ 編譯器中都被支持
    • free 在某些系統開發中不能調用
    • delete 可以觸發析構函數的調用
    • free 僅歸還以前分配的內存空間
    • 對象的銷燬只能使用 delete
    • free 不適合面向對象開發

關於虛函數

構造函數是否能夠成爲虛函數?
析構函數是否能夠成爲虛函數?對象

  • 構造函數不可能成爲虛函數繼承

    • 在構造函數執行結束後,虛函數表指針纔會被正確的初始化
  • 析構函數能夠成爲虛函數

    • 析構函數在對象銷燬以前被調用,意味着虛函數表指針仍然正確的指向虛函數表
    • 建議在設計類時將析構函數聲明爲虛函數

編程實驗: 構造,析構,虛函數

test_1.cpp

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }
    virtual void func()
    {
        cout << "Base::func()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
};

class Derived : public Base
{
public:
    Derived()
    {
        cout << "Derived()" << endl;
    }
    virtual void func()
    {
        cout << "Derived::func()" << endl;
    }
    ~Derived()
    {
        cout << "~Derived()" << endl;
    }
};

int main()
{
    Base* p = new Derived();    // 注意這裏!
    
    // ...
    
    delete p;

    return 0;
}
輸出:
Base()
Derived()
~Base()

分析: 
爲何 子類 的析構函數沒有被調用呢?

Base* p = new Derived(); ==> 由於賦值兼容性,編譯經過。
delete p;                ==> 編譯器考慮安全性,根據指針類型進行對象銷燬

析構函數聲明爲虛函數的意義 1:test_2.cpp

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }
    virtual void func()
    {
        cout << "Base::func()" << endl;
    }
    virtual ~Base()               // 注意這裏!
    {
        cout << "~Base()" << endl;
    }
};

class Derived : public Base
{
public:
    Derived()
    {
        cout << "Derived()" << endl;
    }
    virtual void func()
    {
        cout << "Derived::func()" << endl;
    }
    ~Derived()
    {
        cout << "~Derived()" << endl;
    }
};

int main()
{
    Base* p = new Derived();
    
    // ...
    
    delete p;

    return 0;
}
輸出:
Base()
Derived()
~Derived()
~Base()

分析:
當析構函數爲虛函數時, delete p; 將根據 p 指向的實際對象決定如何調用析構函數。

析構函數發生多態行爲,保證系統資源儘量獲得釋放!

當聲明構造函數爲虛函數時,g++ 報錯: virtual Base() { }
error: constructors cannot be declared virtual

構造函數中是否能夠發生多態?
析構函數中是否能夠發生多態?

  • 構造函數中不可能發生多態行爲

    • 在構造函數執行時,虛函數表指針未正確初始化
  • 析構函數中不可能發生多態行爲

    • 在析構函數執行時,虛函數表指針已經被銷燬

構造函數和析構函數中不能發生多態行爲, 只調用當前類中定義的函數版本!

關於繼承中的強制類型轉換

繼承中如何正確的使用強制類型轉換?

  • dynamic_cast 是與繼承相關的類型轉換關鍵字
  • dynamic_cast 要求相關的類中必須有虛函數
  • 用於直接或間接繼承關係的指針(引用)之間

    • 指針:

      • 轉換成功: 獲得目標類型指針
      • 轉換失敗: 獲得一個空指針
    • 引用:

      • 轉換成功: 獲得目標類型引用
      • 轉換失敗: 獲得一個異常操做信息

  • 編譯器會檢查 dynamic_cast 的使用是否正確
  • 類型轉換的結果只可能在運行階段才能獲得

編程實驗: dynamic_cast 的使用

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }
    virtual ~Base()
    {
        cout << "~Base()" << endl;
    }
};

class Derived : public Base
{
};

int main()
{
    Base* p = new Base();                    // 注意這裏! 
    
    Derived* pd = dynamic_cast<Derived*>(p); // 注意這裏!
    
    if( pd != NULL )
    {
        cout << "pd = " << pd << endl;
    }
    else
    {
        cout << "Cast error!" << endl;
    }

    delete p;

    return 0;
}
輸出:
Base()
Cast error!
~Base()

析構函數聲明爲虛函數的意義 2 :
析構函數被聲明爲虛函數,保證 dynamic_cast 關鍵字能夠被支持,而無需單獨刻意定義其它虛成員函數

小結

  • new / delete 會觸發構造函數或者析構函數
  • 構造函數不能成爲虛函數
  • 析構函數能夠成爲虛函數(推薦析構函數成爲虛函數)
  • 構造函數和析構函數中都沒法產生多態行爲
  • dynamic_cast 是與繼承相關的專用轉換關鍵字

以上內容參考狄泰軟件學院系列課程,請你們保護原創!

相關文章
相關標籤/搜索