1. 程式人生 > >C++解析(25):關於動態內存分配、虛函數和繼承中強制類型轉換的疑問

C++解析(25):關於動態內存分配、虛函數和繼承中強制類型轉換的疑問

cas ror src 一個 聲明 eof struct 定義 namespace

0.目錄

1.動態內存分配

  • 1.1 new和malloc的區別
  • 1.2 delete和free的區別

2.虛函數

  • 2.1 構造函數與析構函數是否可以成為虛函數?
  • 2.2 構造函數與析構函數是否可以發生多態?

3.繼承中的強制類型轉換

4.小結

1.動態內存分配

1.1 new和malloc的區別

new關鍵字與malloc函數的區別:

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

下面的代碼輸出什麽?為什麽?
技術分享圖片

示例——new和malloc的區別:

#include <iostream>
#include <cstdlib>

using namespace std;

class Test
{
public:
    Test()
    {
        cout << "Test::Test()" << endl;
    }
};

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

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
Test::Test()

newmalloc的區別:

  • new在所有C++編譯器中都被支持
  • malloc在某些系統開發中是不能調用
  • new能夠觸發構造函數的調用
  • malloc僅分配需要的內存空間
  • 對象的創建只能使用new
  • malloc不適合面向對象開發

1.2 delete和free的區別

下面的代碼輸出什麽?為什麽?
技術分享圖片

示例——delete和free的區別:

#include <iostream>
#include <cstdlib>

using namespace std;

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

int main()
{
    Test* pn = new Test;
    Test* pm = (Test*)malloc(sizeof(Test));
    
    free(pn);
    free(pm);
    
    return 0;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
Test::Test()
100

可以看到,free不會觸發析構函數,會造成內存泄漏!

deletefree的區別:

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

2.虛函數

2.1 構造函數與析構函數是否可以成為虛函數?

構造函數是否可以成為虛函數析構函數是否可以成為虛函數
構造函數不可能成為虛函數:

  • 在構造函數執行結束後,虛函數表指針才會被正確的初始化

析構函數可以成為虛函數:

  • 建議在設計類時將析構函數聲明為虛函數

示例——不把析構聲明為虛函數:

#include <iostream>

using namespace std;

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


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

int main()
{
    Base* p = new Derived();
    
    cout << endl;
    
    delete p;
    
    return 0;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
Base()
Derived()

~Base()

由於沒有把析構聲明為虛函數,因此,編譯器直接根據指針p的類型來決定調用哪一個析構函數,又由於指針p的類型是父類的,所以編譯器認為直接調用父類的析構函數就可以了。

示例——把析構聲明為虛函數:

#include <iostream>

using namespace std;

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


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

int main()
{
    Base* p = new Derived();
    
    cout << endl;
    
    delete p;
    
    return 0;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
Base()
Derived()

~Derived()
~Base()

如果將構造函數聲明為虛函數,報錯信息如下:

error: constructors cannot be declared virtual

2.2 構造函數與析構函數是否可以發生多態?

構造函數中是否可以發生多態?析構函數中是否可以發生多態?
構造函數中不可能發生多態行為:

  • 在構造函數執行時,虛函數表指針未被正確初始化

析構函數中不可能發生多態行為:

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

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

示例——構造函數和析構函數中不可能發生多態:

#include <iostream>

using namespace std;

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


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

int main()
{
    Base* p = new Derived();
    
    cout << endl;
    
    delete p;
    
    return 0;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
Base()
Base::func()
Derived()
Derived::func()

Derived::func()
~Derived()
Base::func()
~Base()

3.繼承中的強制類型轉換

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

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

  • 指針:
    1. 轉換成功:得到目標類型的指針
    2. 轉換失敗:得到一個空指針
  • 引用:
    1. 轉換成功:得到目標類型的引用
    2. 轉換失敗:得到一個異常操作信息

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

示例——dynamic_cast的使用:

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }
    
    virtual ~Base()
    {
        cout << "Base::~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;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
Base::Base()
Cast error!
Base::~Base()

4.小結

  • new / delete會觸發構造函數或者析構函數的調用
  • 構造函數不能成為虛函數
  • 析構函數可以成為虛函數
  • 構造函數析構函數中都無法產生多態行為
  • dynamic_cast是與繼承相關的專用轉換關鍵字

C++解析(25):關於動態內存分配、虛函數和繼承中強制類型轉換的疑問