您好,登錄后才能下訂單哦!
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分析,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
如果你覺得本文對你有幫助,歡迎轉載,煩請注明出處,謝謝!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。