前言:本篇文章主要介紹了C++中的new/delete、構造/解構函式、dynamic_cast分析 本文通過例項程式碼給大家介紹的非常詳細,具有一定的參考借鑑價值,希望能對你有所幫助。 1,new 關鍵字和 malloc 函式區別(自己、功能、應用):
1,new 關鍵字是 C++ 的一部分:
1,如果是 C++ 編譯器,則肯定可以用 new 申請堆空間記憶體;
2,malloc 是由 C 庫提供的函式:
1,如果沒有相應的庫,malloc 將不能使用;
2,有些特殊的嵌入式開發中,少了 C 庫,則就不能動態記憶體分配;
3,new 以具體型別為單位進行記憶體分配;
1,物件導向中一般用 new,不用 malloc;
4,malloc 以位元組為單位進行記憶體分配;
5,new 在申請記憶體空間時可進行初始化;
1,觸發建構函式呼叫;
6,malloc 僅根據需要申請定量的記憶體空間;
1,物件的建立只能用 new,malloc 不適合物件導向開發;
2,下面程式碼輸出什麼?為什麼?見 new 和 malloc 的區別程式設計實驗:
#include <iostream>#include <string>#include <cstdlib>using namespace std;class Test{ int* mp; //為了說明 free() 可能造成記憶體洩漏問題而新增的成員變數;public: Test() { cout << "Test::Test()" << endl; mp = new int(100); // 申請 4 個位元組堆空間並初始化為 100; cout << *mp << endl; } ~Test() { delete mp; // 解構函式歸還堆空間;但是如果僅僅用 free() 函式歸還堆空間,這裡解構函式沒有呼叫,則物件沒有摧毀,那麼就造成了堆空間洩漏,這在大型專案開發中是不可原諒的; cout << "~Test::Test()" << endl; }};int main(){ Test* pn = new Test; // 第一步申請堆空間,第二步(申請成功後)在堆空間上呼叫建構函式、因為需要初始化; Test* pm = (Test*)malloc(sizeof(Test)); // 這行程式碼執行完後,pm 並沒有指向合法的物件,它僅僅指向一片記憶體空間而已,這個時候這片記憶體空間不能夠成為一片合法的物件,因為就沒有物件; delete pn; // 動態歸還堆空間;第一步 delete 觸發解構函式呼叫,摧毀物件,第二步歸還堆空間;在歸還堆空間的時候,要先摧毀掉物件,否則容易出現記憶體洩漏; free(pm); // 動態規劃堆空間;僅歸還堆空間,不觸發解構函式呼叫;這裡不能用 delete pm,因為這樣會對非法物件呼叫建構函式,而對於解構函式中的 delete mp 來說,這樣的影響是深遠的,不知道什麼時候就會帶來 bug,且不可除錯,只能通過“程式碼走查”的方式來檢查是不是混用了兩種型別的申請釋放堆空間函式; return 0;}複製程式碼
1,結論:
1,free() 可以釋放由 new 申請來的堆空間,但是 free() 不會進行解構函式的呼叫,因此有可能造成記憶體洩漏;
2,new 和 delete,malloc 和 free 只能匹配使用,不能混用;
3,new 和 malloc 的區別(自己、功能、應用):
1,new 在所有 C++ 編譯器中都被支援;
2,malloc 在某些系統開發中是不能呼叫的;
3,new 能夠觸發建構函式的呼叫;
4,malloc 僅分配需要的記憶體空間;
5,物件的建立只能使用 new;
6,malloc 不適合物件導向開發;
4,下面的程式碼輸出什麼?為什麼?
1,程式碼示例:
int main(){ Test* pn = new Test; // 呼叫建構函式; test* pm = (Test*)malloc(sizeof(Test)); // 僅申請堆空間; delete pn; // 呼叫解構函式; free(pm); // 僅釋放堆空間; return 0;}複製程式碼
5,delete 和 free 的區別(自己、功能、應用):
1,delete 在所有 C++ 編譯器中都被支援;
2,free 在某些系統開發中是不能呼叫;
3,delete 能夠觸發解構函式的呼叫;
4,free 僅歸還之前分配的記憶體空間;
5,物件的銷燬只能使用 delete;
6,free 不適合物件導向開發。
6,建構函式是否可以成為虛擬函式?解構函式是否可以成為虛擬函式?
7,建構函式不可能成為虛擬函式:
1,在建構函式執行結束後,虛擬函式表指標才會被正確的初始化;
1,C++ 裡面的多型是通過虛擬函式表和指向虛擬函式表指標完成的,虛擬函式表指標是由編譯器建立的,同時也是由編譯器進行初始化,在建構函式執行結束之後,虛擬函式表的指標才會被正確進行初始化;
2,在建構函式執行的過程當中,虛擬函式表的指標有可能是沒有被正確初始化的,因為對於虛擬函式表和虛擬函式表指標的實現,對於不同的 C++ 編譯器而言,實現有可能不一樣,但是所有的 C++ 編譯器都會保證在建構函式執行結束後,虛擬函式表指標肯定會被正確的初始化,在這之前,是沒有保證的;
3,所以建構函式不可能成為虛擬函式,建立一個物件的時候,我們需要建構函式來初始化虛擬函式表的指標,因此建構函式相當於一個入口點,這個入口點負責虛擬函式呼叫的前期工作,這個入口點當然不可能是虛擬函式;
8,解構函式可以成為虛擬函式:
1,解構函式在物件銷燬之前被呼叫,物件銷燬之前意味著虛擬函式指標是正確的指向對應的虛擬函式表的;
2,建議在設計類時將解構函式宣告為虛擬函式(工程中設計一個父類的解構函式為虛擬函式);
1,賦值相容性申請子類物件給父類指標時,當 delete 作用在指標上時,編譯器會直接根據指標型別(此時是父類)來呼叫相應的解構函式,若父類加上 virtual,編譯器可以根據指標指向的實際物件(此時是子類)決定如何呼叫解構函式(多型);
9,構造、析構、虛擬函式程式設計實驗:
#include <iostream>#include <string>using namespace std;class Base{public: Base() // 若申請為解構函式,則編譯器在此處顯示:error:constructors cannot be declared virtual. { 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; // 期望呼叫完子類解構函式再呼叫父類的解構函式;但是如果父類沒有申請為解構函式,則只呼叫父類解構函式;這是因為此時刪除的是一個父類的指標,由於並沒有將解構函式申請為 virtual,因此在這樣情況下,編譯器直接根據指標 p 的型別來決定呼叫哪一個建構函式,由於指標 p 的型別是父類的型別,所以編譯器直接暴力認為呼叫父類建構函式就可以了;當將父類的虛擬函式宣告為 virtual 時,編譯器就不會簡單的根據指標 p 的型別來簡單呼叫父類的或者是子類的解構函式了,這個時候由於解構函式是虛擬函式,所以在執行這行程式碼的時候,編譯器會根據指標 p 指向的實際物件來決定如何呼叫解構函式,這是多型; return 0;}複製程式碼
1,工程中設計一個類作為父類出現時,我們都要將解構函式宣告為虛擬函式,否 則就有可能產生記憶體洩漏,因為有可能跳過子類解構函式的呼叫,如果子類 解構函式中有釋放資源的操作(動態記憶體空間),則後果不堪設想;
10,建構函式中是否可以發生多型?解構函式中是否可以發生多型?
11,建構函式中(建構函式中呼叫虛擬函式)不可能發生多型行為:
1,在建構函式執行時,虛擬函式表指標未被正確初始化;
12,解構函式中(解構函式中呼叫虛擬函式)不可能發生多型行為:
1,在解構函式執行時,虛擬函式表指標可能已經被摧毀;
13,解構函式和建構函式中(呼叫虛擬函式時)不能發生多型行為,只呼叫當前類中的函式版本;
1,建構函式和解構函式中呼叫虛擬函式實驗:
#include <iostream>#include <string>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(); // 列印 Base(),Base::func(),Derived(),Derived::func(), // ... delete p; // 列印 Derived::func(),~Derived(),Base::func(), ~Base(); return 0;}複製程式碼
14,繼承中如何正確的使用強制型別轉換?
1,dynamic_cast 是與繼承相關的型別轉換關鍵字;
2,dynamic_cast 要求相關的類中必須有虛擬函式;
3,用於有直接或者間接繼承關係的指標(引用)之間;
1,指標:
1,轉換成功:得到目標型別的指標;
2,轉換失敗:得到一個空指標;
2,引用:
1,轉換成功:得到目標型別的引用;
2,轉換失敗:得到一個異常操作資訊;
4,編譯器會檢查 dynamic_cast 的使用是否正確;
1,在 C++ 編譯器中得到足夠重視,是非常有地位的一個型別轉換關鍵字;
2,使用不正確編譯器會報錯;
5,型別轉換的結果只可能在執行階段才能得到;
1,動態的型別轉換,轉換結果執行階段才能得到;
15,dynamic_cast 的使用程式設計實驗:
#include <iostream>#include <string>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 Derived; Derived* pd = p; // 編譯器顯示:error:invalid conversion from 'Base*' to 'Derived*'; // 未有虛擬函式時,用 dynamic_cast 轉換,編譯器顯示:error: cannot dynamic_cast 'p' (of type 'class Base*') to type 'Derived*' (source type is not polymorphic(多型的)) // 有虛擬函式且用了 dynamic_cast 也要判斷 pd 不為空;*/ Base* p = new Base; Derived* pd = dynamic_cast<Derived*>(p); // 不合法,不能使用子類指標,指向父類物件;編譯器編譯階不報錯;但是執行時 pd = 0; 意味著此處強制型別轉換不成功; if( pd != NULL ) // 這樣的判斷很有必要; { cout << "pd = " << pd << endl; } else { cout << "Cast error!" << endl; } delete p; return 0;}複製程式碼
以上所述是小編給大家介紹的C++中的new/delete、構造/解構函式、dynamic_cast分析,希望對大家有所幫助。看到這裡各位迷茫的朋友是不是對C有了一定的瞭解了呢,感興趣的同學可以加下小編的c/c++學習交流基地,小編蒐集了一套0基礎系統性學習C語言的資料,如果各位對C語言感興趣的話可以加裙即可免費領取一套學習資料哦!!!!