本篇是上一篇的續篇,接著為大家介紹C++中的指針。
在堆上分配內存
前面已經說過,所謂的在堆上分配就是運行時期向操作系統申請內存,而要向操作系統申請內存,不同的操作系統提供了不同的接口,具有不同的申請內存的方式,而這主要通過需調用的函數原型不同來表現。由於C++是一門語言,不應該是操作系統相關的,所以C++提供了一個統一的申請內存的接口,即new操作符。如下:
- unsigned long *pA = new unsigned long;
- *pA = 10;
- unsigned long *pB = new unsigned long[ *pA ];
上面就申請了兩塊內存,pA所指的內存即pA的值所對應的內存)是4字節大小,而pB所指的內存是4*10=40字節大小。應該注意,由於new是一個操作符,其結構為new <類型名>[<整型數字>].它返回指針類型的數字,其中的<類型名>指明了什麼樣的指針類型,而後面方括號的作用和定義數組時一樣,用於指明元素的個數,但其返回的並不是數組類型,而是指針類型。
應該注意上面的new操作符是向操作系統申請內存,並不是分配內存,即其是有可能失敗的。當內存不足或其他原因時,new有可能返回數值為0的指針類型的數字以表示內存分配失敗。即可如下檢測內存是否分配成功。
- unsigned long *pA = new unsigned long[10000];
- if !pA )? // 內存失敗!做相應的工作
上面的if是判斷語句,下篇將介紹。如果pA為0,則!pA的邏輯取反就是非零,故為邏輯真,進而執行相應的工作。
只要分配了內存就需要釋放內存,這雖然不是必須的,但是作為程序員,它是一個良好習慣資源是有限的)。為了釋放內存,使用delete操作符,如下:
- delete pA;
- delete[] pB;
注意delete操作符並不返回任何數字,但是其仍被稱作操作符,看起來它應該被叫做語句更加合適,但為了滿足其依舊是操作符的特性,C++提供了一種很特殊的數字類型——void.其表示無,即什麼都不是,因此delete其實是要返回數字的,只不過返回的數字類型為void罷了。
注意上面對pA和pB的釋放不同,因為pA按照最開始的書寫,是new unsigned long返回的,而pB是new unsigned long[ *pA ]返回的。所以需要在釋放pB時在delete的後面加上“[]”以表示釋放的是數組,不過在VC中,不管前者還是後者,都能正確釋放內存,無需“[]”的介入以幫助編譯器來正確釋放內存,因為以Windows為平台而開發程序的VC是按照Windows操作系統的方式來進行內存分配的,而Windows操作系統在釋放內存時,無需知道欲釋放的內存塊的長度,因為其已經在內部記錄下來這種說法並不准確,實際應是C運行時期庫干了這些事,但其又是依賴於操作系統來干的,即其實是有兩層對內存管理的包裝,在此不表)。
類型修飾符type-specifier)
類型修飾符,即對類型起修飾作用的符號,在定義變量時用於進一步指明如何操作變量對應的內存。因為一些通用操作方式,即這種操作方式對每種類型都適用,故將它們單獨分離出來以方便代碼的編寫,就好像水果。吃蘋果的果肉、吃梨的果肉,不吃蘋果的皮、不吃梨的皮。這裡蘋果和梨都是水果的種類,相當於類型,而“XXX的果肉”、“XXX的皮”就是用於修飾蘋果或梨這種類型用的,以生成一種新的類型——蘋果的果肉、梨的皮,其就相當於類型修飾符。
本文所介紹的數組和指針都是類型修飾符,之前提過的引用變量的“&”也是類型修飾符。
類型修飾符只在定義變量時起作用,如前面的
- unsigned long a, b[10], *pA = &a, &rA = a;
這裡就使用了上面的三個類型修飾符——“[]”、“*”和“&”。上面的unsigned long暫且叫作原類型,表示未被類型修飾符修飾以前的類型。下面分別說明這三個類型修飾符的作用。
數組修飾符“[]”——其總是接在變量名的後面,方括號中間放一整型數c以指明數組元素的個數,以表示當前類型為原類型c個元素連續存放,長度為原類型的長度乘以c.因此long a[10];就表示a的類型是10個long類型元素連續存放,長度為10*4=40字節。而long a[10][4];就表示a是4個long[10]類型的元素連續存放,其長度為4*40=160字節。
相信已經發現,由於可以接多個“[]”,因此就有了計算順序的關系,為什麼不是10個long[4]類型的元素連續存放而是倒過來?類型修飾符的修飾順序是從左向右進行計算的。故short *a[10];表示的是10個類型為short*的元素連續存放,長度為10*4=40字節。
指針修飾符“*”——其總是接在變量名的前面,表示當前類型為原類型的指針。故:
- short a = 10, *pA = &a, **ppA = &pA;
注意這裡的ppA被稱作多級指針,即其類型為short的指針的指針,也就是short**.而short **ppA = &pA;的意思就是計算pA的地址的值,得一類型為short*的地址類型的數字,然後“&”操作符將此數字轉成short*的指針類型的數字,最後賦值給變量ppA.
如果上面很昏,不用去細想,只要注意類型匹配就可以了,下面簡要說明一下:假設a的地址為2000,則pA的地址為2002,ppA的地址為2006.
對於
- pA = &a;
先計算“&a”的值,因為a等同於地址,則“&”發揮作用,直接將a的地址這個數字轉成long*類型並返回,然後賦值給pA,則pA的值為2000.
對於
- ppA = &pA;
先計算“&pA”的值,因為pA等同於地址,則“&”發揮作用,直接將pA的地址這個數字轉成long**類型因為pA已經是long*的類型了)並返回,然後賦值給ppA,則ppA的值為2002.
引用修飾符“&”——其總是接在變量名的前面,表示此變量不用分配內存以和其綁定,而在說明類型時,則不能有它,下面說明。由於表示相應變量不用分配內存以生成映射,故其不像上述兩種類型修飾符,可以多次重復書寫,因為沒有意義。且其一定在“*”修飾符的右邊,即可以
- long **&a = ppA;
但不能
- long *&*a;
- 或
- long &**a;
因為按照從左到右的修飾符計算順序,long*&*表示long的指針的引用的指針,引用只是告知編譯器不要為變量在棧上分配內存,實際與類型無關,故引用的指針是無意義的。
而long&**則表示long的引用的指針的指針,同上,依舊無意義。同樣
- long &a[40];//錯誤的
因為其表示分配一塊可連續存放類型為long的引用的40個元素的內存,引用只是告知編譯器一些類型無關信息的一種手段,無法作為類型的一種而被實例化。
應該注意引用並不是類型但出於方便,經常都將long的引用稱作一種類型),而
- long **&rppA = &pA;是錯誤的
因為上句表示的是不要給變量rppA分配內存,直接使用“=”後面的地址作為其對應的地址,而&pA返回的並不是地址類型的數字,而是指針類型,故編譯器將報類型不匹配的錯誤。
但是即使
- long **&rppA = pA;
也同樣失敗,因為long*和long**是不同的,不過由於類型的匹配,下面是可以的:
- long a = 10, *pA = &a, **ppA = &pA, *&rpA1 = *ppA, *&rpA2 = * ppA + 1 );
類型修飾符和原類型組合在一起以形成新的類型,如long*&、short *[34]等,都是新的類型,應注意前面new操作符中的<類型名>要求寫入類型名稱,則也可以寫上前面的long*等,即:
- long **ppA = new long*[45];
即動態分配一塊4*45=180字節的連續內存空間,並將首地址返回給ppA.同樣也就可以:
- long ***pppA = new long**[2];
- 而
- long **pA)[10] = new long*[20][10];
也許看起來很奇怪,其中的pA的類型為long **)[10],表示是一個有10個long*元素的數組的指針,而分配的內存的長度為4*10)*20=800字節。因為數組修飾符“[]”只能放在變量名後面,而類型修飾符又總是從左朝右計算,則想說明是一個10個long元素的數組的指針就不行,因為放在左側的“*”總是較右側的“[]”先進行類型修飾。
故C++提出上面的語法,即將變量名用括號括起來,表示裡面的類型最後修飾,
故:
- long *a)[10];
- 等同於
- long *a[10];
而
- long *&aa)[10] = a;
也才能夠正確,否則按照前面的規則,使用
- long *&aa[10] = a;
將報錯前面已說明原因)。而
- long **pA)[10] = &a;
也就能很正常地表示我們需要的類型了。因此還可以
- long **&rpA)[10] = pA;
- 以及
- long ***ppA)[10] = &pA;
希望通過本文的介紹,能夠讓你清楚的明白C++中的指針概念以及用法。