關於動態記憶體分配
new 和 malloc 的區別是什麼?
delete 和 free 的區別是什麼?
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 :
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 的區別
- 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 是與繼承相關的專用轉換關鍵字
以上內容參考狄泰軟體學院系列課程,請大家保護原創!