1. 程式人生 > >【C++】C++用new和不用new建立類物件區別

【C++】C++用new和不用new建立類物件區別

起初剛學C++時,很不習慣用new,後來看老外的程式,發現幾乎都是使用new,想一想區別也不是太大,但是在大一點的專案設計中,有時候不使用new的確會帶來很多問題。

當然這都是跟new的用法有關的。new建立類物件,使用完後需使用delete刪除,跟申請記憶體類似。所以,new有時候又不太適合,比如在頻繁呼叫場合,使用區域性new類物件就不是個好選擇,使用全域性類物件或一個經過初始化的全域性類指標似乎更加高效。

一、new建立類物件與不new區別

下面是自己總結的一些關於new建立類物件特點:

  • new建立類物件需要指標接收,一處初始化,多處使用
  • new建立類物件使用完需delete銷燬
  • new建立物件直接使用堆空間
    ,而區域性不用new定義類物件則使用棧空間
  • new物件指標用途廣泛,比如作為函式返回值、函式引數等
  • 頻繁呼叫場合並不適合new,就像new申請和釋放記憶體一樣

二、new建立類物件例項

1、new建立類物件例子:

CTest* pTest = new CTest();

delete pTest;

pTest用來接收類物件指標。

不用new,直接使用類定義申明:

CTest mTest;

此種建立方式,使用完後不需要手動釋放,該類解構函式會自動執行。而new申請的物件,則只有呼叫到delete時再會執行解構函式,如果程式退出而沒有執行delete則會造成記憶體洩漏。

2、只定義類指標

這跟不用new申明物件有很大區別,類指標可以先行定義,但類指標只是個通用指標,在new之前併為該類物件分配任何記憶體空間。比如:

CTest* pTest = NULL;

但使用普通方式建立的類物件,在建立之初就已經分配了記憶體空間。而類指標,如果未經過物件初始化,則不需要delete釋放。

3、new物件指標作為函式引數和返回值

下面是天緣隨手寫一個例子,不太嚴謹。主要示意一下類指標物件作為返回值和引數使用。

示例:

類的記憶體分配簡單總結,程式碼片段如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> usingnamespace std; /** * yanggang * http://blog.ithomer.net * 2014-08-11 **/ classClassA { private: intA; intB; voidprin1() { } voidprin2() { } virtualvoid prin3() { } }; classClassB : publicClassA { public: intC; intD; voidprin4() { } voidprin5() { } virtual void  prin6() { } }; intmain(intargc, char* argv[]) { cout<<sizeof(ClassA)<<endl;       // 16 cout<<sizeof(ClassB)<<endl;       // 24 return0; }

測試環境:

Ubuntu 12.04.5 LTS   x86_64 GNU/Linux

g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 編譯引數 -m64 

結果是

[email protected]:~/Desktop$ g++ testMem.c -o testMem && ./testMem
16
24

結果為什麼是這樣?

32位系統int4個位元組,64位系統int也佔4個位元組(不是8個位元組),而一個類中所有的虛擬函式通過一個虛擬函式指標管理,類物件的大小隻包含這個vptr指標(在64位機器上指標sizeof為8個位元組),其他虛擬函式是放在別的記憶體空間中管理,vptr指標在64位機器上是8個位元組大小(32位機器上是4個位元組)。注意到普通成員函式並不佔類物件的大小空間,因為普通成員函式通過this指標管理,一個物件的this指標並不是物件本身的一部分,不會影響sizeof(物件)的結果。

sizeof(ClassA)

1)int A 和 int B 各佔4個位元組,考慮64位機器編譯器對其規則,合併為8個位元組

2)virtual void prin3() 虛擬函式的vptr指標,在64位機器編譯器上佔8個位元組

3)合計 sizeof(ClassA)為 8 + 8 = 16個位元組

this作用域是在類內部,當在類的非靜態成員函式中訪問類的非靜態成員的時候,編譯器會自動將物件本身的地址作為一個隱含引數傳遞給函式。這個this指標會因編譯器不同而有不同的放置位置可能是棧,也可能是暫存器,甚至全域性變數。

子類其實不管如何繼承,用sizeof()算該類的大小都會把父類中的私有成員變數所佔的空間算進去,也就是說,私有變數也在子類中分配了記憶體,但你卻不可以直接訪問,這起到一個保護作用,這如同一個珠寶,共有繼承就是開放性的展覽,而私有繼承是把珠寶鎖起來,你卻不能動,要動珠寶如果有管家(基類的public中定義了一些對其私有變數操作的成員函式,)只能讓管家幫你代勞。

sizeof(ClassB)

1)int A 和 int B 各佔4個位元組,是父類ClassA中的私有變數,合併佔用8個位元組

2)virtual void prin3() 虛擬函式的vptr指標,父類ClassA在子類中不會分配空間

3)int C 和 int D 各佔4個位元組,在子類中會分配空間,合併佔用8個位元組

4)virtual void prin6() 虛擬函式的vptr指標,在64位機器編譯器上佔8個位元組

5)合計 sizeof(ClassB)為 8 + 8 + 8 = 24個位元組

註明: 上述示例在32位編譯器上測試,sizeof(ClassA)和sizeof(ClassB)分別為12和20位元組(虛擬函式指標佔用空間少了4位元組)

只有虛擬函式會佔用一個指標大小的記憶體,原因是系統用一個指標維護這個類的虛擬函式表,並且注意這個虛擬函式無論含有多少項(類中含有多個虛擬函式)都不會影響類的大小。

知識延伸:

1) 空 ClassA(驗證空Class佔用空間大小

1 2 3 4 5 6 7 8 9 10 11 classClassA { private: voidprin1() { } voidprin2() { } }; cout<<sizeof(ClassA)<<endl;       // 1

2) ClassA 只有一個char(驗證編譯器對其規則

1 2 3 4 5 6 7 8 9 10 11 12 classClassA { private: charC; voidprin1() { } voidprin2() { } }; cout<<sizeof(ClassA)<<endl;       // 1

3)ClassA 有一個char和一個虛擬函式(或一個long型)(驗證編譯器對其規則二

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 classClassA { private: charC; voidprin1() { } voidprin2() { } virtualvoid prin3() { } }; cout<<sizeof(ClassA)<<endl;       // 16

4)ClassA 有一個char和一個虛擬函式(或一個long型),且對調char和虛擬函式的先後順序(驗證編譯器對其規則三

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 classClassA { voidprin1() { } voidprin2() { } virtualvoid prin3() { } private: charC; }; cout<<sizeof(ClassA)<<endl;       // 16

5)ClassA 有一個char,一個int,一個虛擬函式(或一個long型)(驗證編譯器對其規則四

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 classClassA { private: charC; intA; voidprin1() { } voidprin2() { } virtualvoid prin3() { } }; cout<<sizeof(ClassA)<<endl;       // 16

6)ClassA 有一個char,一個int(驗證編譯器對其規則五

1 2 3 4 5 6 7 8 9 10 11 12 13 classClassA { private: charC; intA; voidprin1() { } voidprin2() { } }; cout<<sizeof(ClassA)<<endl;       // 8

7)ClassA 只有一個int(驗證編譯器對其規則六

1 2 3 4 5 6 7 8 9 10 11 12 classClassA { private: intA; voidprin1() { } voidprin2() { } }; cout<<sizeof(ClassA)<<endl;       // 4

總結如下:

1) 空類的 sizeof 為1個位元組

2) 只有一個char的類,sizeof為一個位元組

3) 類中含有char和虛擬函式,將以最大的變數或指標為編譯器對齊規則,例如:虛擬函式指標佔8個位元組(64位編譯器),則char雖然只佔1個位元組,但對齊後空餘了7個位元組,合併類佔8(指標) + 1(char) + 7(對齊的空位元組) = 16個位元組

4) 對齊規則,跟變數或虛擬函式的先後順序無關,只跟最大變數型別或函式指標有關,函式指標跟編譯器最大對齊位數有關(不太好理解,請繼續往下看)