C++從零開始(五)——何謂指針

 

    本篇說明C++中的重中又重的關鍵——指針類型,並說明兩個很有意義的概念——靜態和動態。

    數組

    前面說了在C++中是通過變量來對內存進行訪問的,但根據前面的說明,C++中只能通過變量來操作內存,也就是說要操作某塊內存,就必須先將這塊內存的首地址和一個變量名綁定起來,這是很糟糕的。比如有100塊內存用以記錄100個工人的工資,現在要將每個工人的工資增加5%,爲了知道各個工人增加了後的工資爲多少,就定義一個變量float a1;,用其記錄第1個工人的工資,然後執行語句a1 += a1 * 0.05f;,則a1裏就是增加後的工資。由於是100個工人,所以就必須有100個變量,分別記錄100個工資。因此上面的賦值語句就需要有100條,每條僅僅變量名不一樣。

    上面需要手工重複書寫變量定義語句float a1;100遍(每次變一個變量名),無謂的工作。因此想到一次向操作系統申請100*4=400個字節的連續內存,那麼要給第i個工人修改工資,只需從首地址開始加上4*i個字節就行了(因爲float佔用4個字節)。

    爲了提供這個功能,C++提出了一種類型——數組。數組即一組數字,其中的各個數字稱作相應數組的元素,各元素的大小一定相等(因爲數組中的元素是靠固定的偏移來標識的),即數組表示一組相同類型的數字,其在內存中一定是連續存放的。在定義變量時,要表示某個變量是數組類型時,在變量名的後面加上方括號,在方括號中指明欲申請的數組元素個數,以分號結束。因此上面的記錄100個工資的變量,即可如下定義成數組類型的變量:

    float a[100];

    上面定義了一個變量a,分配了100*4=400個字節的連續內存(因爲一個float元素佔用4個字節),然後將其首地址和變量名a相綁定。而變量a的類型就被稱作具有100個float類型元素的數組。即將如下解釋變量a所對應內存中的內容(類型就是如何解釋內存的內容):a所對應的地址標識的內存是一塊連續內存的首地址,這塊連續內存的大小剛好能容納下100個float類型的數字。

    因此可以將前面的float b;這種定義看成是定義了一個元素的float數組變量b.而爲了能夠訪問數組中的某個元素,在變量名後接方括號,方括號中放一數字,數字必須是非浮點數,即使用二進制原碼或補碼進行表示的數字。如a[ 5 + 3 ] += 32;就是數組變量a的第5 + 3個元素的值增加32.又:

    long c = 23; float b = a[ ( c – 3 ) / 5 ] + 10, d = a[ c – 23 ];

    上面的b的值就爲數組變量a的第4個元素的值加10,而d的值就爲數組變量a的第0個元素的值。即C++的數組中的元素是以0爲基本序號來記數的,即a[0]實際代表的是數組變量a中的第一個元素的值,而之所以是0,表示a所對應的地址加上0*4後得到的地址就爲第一個元素的地址。

    應該注意不能這樣寫:long a[0];,定義0個元素的數組是無意義的,編譯器將報錯,不過在結構或類或聯合中符合某些規則後可以這樣寫,那是C語言時代提出的一種實現結構類型的長度可變的技術,在《C++從零開始(九)》中將說明。

    還應注意上面在定義數組時不能在方括號內寫變量,即long b = 10; float a[ b ];是錯誤的,因爲編譯此代碼時,無法知道變量b的值爲多少,進而無法分配內存。可是前面明明已經寫了b = 10;,爲什麼還說不知道b的值?那是因爲無法知道b所對應的地址是多少。因爲編譯器編譯時只是將b和一個偏移進行了綁定,並不是真正的地址,即b所對應的可能是Base - 54,而其中的Base就是在程序一開始執行時動態向操作系統申請的大塊內存的尾地址,因爲其可能變化,故無法得知b實際對應的地址(實際在Windows平臺下,由於虛擬地址空間的運用,是可以得到實際對應的虛擬地址,但依舊不是實際地址,故無法編譯時期知道某變量的值)。

    但是編譯器仍然可以根據前面的long b = 10;而推出Base - 54的值爲10啊?重點就是編譯器看到long b = 10;時,只是知道要生成一條指令,此指令將10放入Base - 54的內存中,其它將不再過問(也沒必要過問),故即使才寫了long b = 10;編譯器也無法得知b的值。

    上面說數組是一種類型,其實並不準確,實際應爲——數組是一種類型修飾符,其定義了一種類型修飾規則。關於類型修飾符,後面將詳述。

    字符串

    在《C++從零開始(二)》中已經說過,要查某個字符對應的ASCII碼,通過在這個字符的兩側加上單引號,如'A'就等同於65.而要表示多個字符時,就使用雙引號括起來,如:"ABC".而爲了記錄字符,就需要記錄下其對應的ASCII碼,而ASCII碼的數值在-128到127以內,因此使用一個char變量就可以記錄一個ASCII碼,而爲了記錄"ABC",就很正常地使用一個char的數組來記錄。如下:

    char a = 'A'; char b[10]; b[0] = 'A'; b[1] = 'B'; b[2] = 'C';

    上面a的值爲65,b[0]的值爲65,b[1]爲66,b[2]爲67.因爲b爲一個10元素的數組,在這其記錄了一個3個字符長度的字符串,但是當得到b的地址時,如何知道其第幾個元素纔是有效的字符?如上面的b[4]就沒有賦值,那如何知道b[4]不應該被解釋爲字符?可以如下,從第0個元素開始依次檢查每個char元素的值,直到遇到某個char元素的值爲0(因爲ASCII碼錶中0沒有對應的字符),則其前面的所有的元素都認爲是應該用ASCII碼錶來解釋的字符。故還應b[3] = 0;以表示字符串的結束。

    上面的規則被廣泛運用,C運行時期庫中提供的所有有關字符串的操作都是基於上面的規則來解釋字符串的(關於C運行時期庫,可參考《C++從零開始(十九)》)。但上面爲了記錄一個字符串,顯得煩瑣了點,字符串有多長就需要寫幾個賦值語句,而且還需要將末尾的元素賦值爲0,如果搞忘則問題嚴重。對於此,C++強制提供了一種簡寫方式,如下:

    char b[10] = "ABC";

    上面就等效於前面所做的所有工作,其中的"ABC"是一個地址類型的數字(準確的說是一初始化表達式,在《C++從零開始(九)》中說明),其類型爲char[4],即一個4個元素的char數組,多了一個末尾元素用於放0來標識字符串的結束。應當注意,由於b爲char[10],而"ABC"返回的是char[4],類型並不匹配,需要隱式類型轉換,但實際沒有進行轉換,而是做了一系列的賦值操作(就如前面所做的工作),這是C++硬性規定的,稱爲初始化,且僅僅對於數組定義時進行初始化有效,即如下是錯誤的:

    char b[10]; b = "ABC";

    而即使是char b[4]; b = "ABC";也依舊錯誤,因爲b的類型是數組,表示的是多個元素,而對多個元素賦值是未定義的,即:float d[4]; float dd[4] = d;也是錯誤的,因爲沒定義d中的元素是依次順序放到dd中的相應各元素,還是倒序放到,所以是不能對一個數組類型的變量進行賦值的。

    由於現在字符的增多(原來只用英文字母,現在需要能表示中文、日文等多種字符),原來使用char類型來表示字符,最多也只能表示255種字符(0用來表示字符串結束),所以出現了所謂的多字節字符串(MultiByte),用這種表示方式記錄的文本文件稱爲是MBCS格式的,而原來使用char類型進行表示的字符串稱爲單字節字符串(SingleByte),用這種表示方式記錄的文本文件稱爲是ANSI格式的。

    由於char類型可以表示負數,則當從字符串中提取字符時,如果所得元素的數值是負的,則將此元素和下一個char元素聯合起來形成一short類型的數字,再按照Unicode編碼規則(一種編碼規則,等同於前面提過的ASCII碼錶)來解釋這個short類型的數字以得到相應的字符。

    而上面的"ABC"返回的就是以多字節格式表示的字符串,因爲沒有漢字或特殊符號,故好象是用單字節格式表示的,但如果:char b[10] = "AB漢C";,則b[2]爲-70,b[5]爲0,而不是想象的由於4個字符故b[4]爲0,因爲“漢”這個字符佔用了兩個字節。

    上面的多字節格式的壞處是每個字符的長度不固定,如果想取字符串中的第3個字符的值,則必須從頭開始依次檢查每個元素的值而不能是3乘上某個固定長度,降低了字符串的處理速度,且在顯示字符串時由於需要比較檢查當前字符的值是否小於零而降低效率,故又推出了第三種字符表示格式:寬字節字符串(WideChar),用這種表示方式記錄的文本文件稱爲是Unicode格式的。其與多字節的區別就是不管這個字符是否能夠用ASCII表示出來,都用一個short類型的數字來表示,即每個字符的長度固定爲2字節,C++對此提供了支持。

    short b[10] = L"AB漢C";

    在雙引號的前面加上“L”(必須是大寫的,不能小寫)即告訴編譯器此雙引號內的字符要使用Unicode格式來編碼,故上面的b數組就是使用Unicode來記錄字符串的。同樣,也有:short c = L'A';,其中的c爲65.

    如果上面看得不是很明白,不要緊,在以後舉出的例子中將會逐漸瞭解字符串的使用的。

 

 

    靜態和動態

    上面依然沒有解決根本問題——C++依舊只能通過變量這個映射元素來訪問內存,在訪問某塊內存前,一定要先建立相應的映射,即定義變量。有什麼壞處?讓我們先來了解靜態和動態是什麼意思。

    收銀員開發票,手動,則每次開發票時,都用已經印好的發票聯給客人開發票,發票聯上只印了4個格子用以記錄商品的名稱,當客人一次買的商品超過4種以上時,就必須開兩張或多張發票。這裏發票聯上的格子的數量就被稱作靜態的,即無論任何時候任何客人買東西,開發票時發票聯上都印着4個記錄商品名稱用的格子。

    超市的收銀員開發票,將商品名稱及數量等輸入電腦,然後即時打印出一張發票給客人,則不同的客人,打印出的發票的長度可能不同(有的客人買得多而有的少),此時發票的長度就稱爲動態的,即不同時間不同客人買東西,開出的發票長度可能不同。

    程序無論執行多少遍,在申請內存時總是申請固定大小的內存,則稱此內存是靜態分配的。前面提出的定義變量時,編譯器幫我們從棧上分配的內存就屬於靜態分配。每次執行程序,根據用戶輸入的不同而可能申請不同大小的內存時,則稱此內存是動態分配的,後面說的從堆上分配就屬於動態分配。

    很明顯,動態比靜態的效率高(發票長度的利用率高),但要求更高——需要電腦和打印機,且需要收銀員的素質較高(能操作電腦),而靜態的要求就較低,只需要已經印好的發票聯,且也只需收銀員會寫字即可。

    同樣,靜態分配的內存利用率不高或運用不夠靈活,但代碼容易編寫且運行速度較快;動態分配的內存利用率高,不過編寫代碼時要複雜些,需自己處理內存的管理(分配和釋放)且由於這種管理的介入而運行速度較慢並代碼長度增加。

    靜態和動態的意義不僅僅如此,其有很多的深化,如硬編碼和軟編碼、緊耦合和鬆耦合,都是靜態和動態的深化。

    地址

    前面說過“地址就是一個數字,用以唯一標識某一特定內存單元”,而後又說“而地址就和長整型、單精度浮點數這類一樣,是數字的一種類型”,那地址既是數字又是數字的類型?不是有點矛盾嗎?如下:浮點數是一種數——小數——又是一種數字類型。即前面的前者是地址實際中的運用,而後者是由於電腦只認識狀態,但是給出的狀態要如何處理就必須通過類型來說明,所以地址這種類型就是用來告訴編譯器以內存單元的標識來處理對應的狀態。

    指針

    已經瞭解到動態分配內存和靜態分配內存的不同,現在要記錄用戶輸入的定單數據,用戶一次輸入的定單數量不定,故選擇在堆上分配內存。假設現在根據用戶的輸入,需申請1M的內存以對用戶輸入的數據進行臨時記錄,則爲了操作這1M的連續內存,需記錄其首地址,但又由於此內存是動態分配的,即其不是由編譯器分配(而是程序的代碼動態分配的),故未能建立一變量來映射此首地址,因此必須自己來記錄此首地址。

    因爲任何一個地址都是4個字節長的二進制數(對32位操作系統),故靜態分配一塊4字節內存來記錄此首地址。檢查前面,可以將首地址這個數據存在unsigned long類型的變量a中,然後爲了讀取此1M內存中的第4個字節處的4字節長內存的內容,通過將a的值加上4即可獲得相應的地址,然後取出其後連續的4個字節內存的內容。但是如何編寫取某地址對應內存的內容的代碼呢?前面說了,只要返回地址類型的數字,由於是地址類型,則其會自動取相應內容的。但如果直接寫:a + 4,由於a是unsigned long,則a + 4返回的是unsigned long類型,不是地址類型,怎麼辦?

    C++對此提出了一個操作符——“*”,叫做取內容操作符(實際這個叫法並不準確)。其和乘號操作符一樣,但是它只在右側接數字,即*( a + 4 )。此表達式返回的就是把a的值加上4後的unsigned long數字轉成地址類型的數字。但是有個問題:a + 4所表示的內存的內容如何解釋?即取1個字節還是2個字節?以什麼格式來解釋取出的內容?如果自己編寫彙編代碼,這就不是問題了,但現在是編譯器代我們編寫彙編代碼,因此必須通過一種手段告訴編譯器如何解釋給定的地址所對內存的內容。

    C++對此提出了指針,其和上面的數組一樣,是一種類型修飾符。在定義變量時,在變量名的前面加上“*”即表示相應變量是指針類型(就如在變量名後接“[]”表示相應變量是數組類型一樣),其大小固定爲4字節。如:unsigned long *pA;

    上面pA就是一個指針變量,其大小因爲是爲32位操作系統編寫代碼故爲4字節,當*pA;時,先計算pA的值,就是返回從pA所對應地址的內存開始,取連續4個字節的內容,然後計算“*”,將剛取到的內容轉成unsigned long的地址類型的數字,接着計算此地址類型的數字,返回以原碼格式解釋其內容而得到一個unsigned long的數字,最後計算這個unsigned long的數字而返回以原碼格式解釋它而得的二進制數。

    也就是說,某個地址的類型爲指針時,表示此地址對應的內存中的內容,應該被編譯器解釋成一個地址。

    因爲變量就是地址的映射,每個變量都有個對應的地址,爲此C++又提供了一個操作符來取某個變量的地址——“&”,稱作取地址操作符。其與“數字與”操作符一樣,不過它總是在右側接數字(而不是兩側接數字)。

    “&”的右側只能接地址類型的數字,它的計算(Evaluate)就是將右側的地址類型的數字簡單的類型轉換成指針類型並進而返回一個指針類型的數字,正好和取內容操作符“*”相反。

    上面正常情況下應該會讓你很暈,下面釋疑。

    unsigned long a = 10, b, *pA; pA = &a; b = *pA; ( *pA )++;

    上面的第一句通過“*pA”定義了一個指針類型的變量pA,即編譯器幫我們在棧上分配了一塊4字節的內存,並將首地址和pA綁定(即形成映射)。然後“&a”由於a是一個變量,等同於地址,所以“&a”進行計算,返回一個類型爲unsigned long*(即unsigned long的指針)的數字。

    應該注意上面返回的數字雖然是指針類型,但是其值和a對應的地址相同,但爲什麼不直接說是unsigned long的地址的數字,而又多一個指針類型在其中攪和?因爲指針類型的數字是直接返回其二進制數值,而地址類型的數字是返回其二進制數值對應的內存的內容。因此假設上面的變量a所對應的地址爲2000,則a;將返回10,而&a;將返回2000.

    看下指針類型的返回值是什麼。當書寫pA;時,返回pA對應的地址(按照上面的假設就應該是2008),計算此地址的值,返回數字2000(因爲已經pA = &a;),其類型是unsigned long*,然後對這個unsigned long*的數字進行計算,直接返回2000所對應的二進制數(注意前面紅字的內容)。

    再來看取內容操作符“*”,其右接的數字類型是指針類型或數組類型,它的計算就是將此指針類型的數字直接轉換成地址類型的數字而已(因爲指針類型的數字和地址類型的數字在數值上是相同的,僅僅計算規則不同)。所以:b = *pA;返回pA對應的地址,計算此地址的值,返回類型爲unsigned long*的數字2000,然後“*pA”返回類型unsigned long的地址類型的數字2000,然後計算此地址類型的數字的值,返回10,然後就只是簡單地賦值操作了。同理,對於++( *pA )(由於“*”的優先級低於前綴++,所以加“()”),先計算“*pA”而返回unsigned long的地址類型的數字2000,然後計算前綴++,最後返回unsigned long的地址類型的數字2000.

    如果你還是未能理解地址類型和指針類型的區別,希望下面這句能夠有用:地址類型的數字是在編譯時期給編譯器用的,指針類型的數字是在運行時期給代碼用的。如果還是不甚理解,在看過後面的類型修飾符一節後希望能有所幫助。

    在堆上分配內存

    前面已經說過,所謂的在堆上分配就是運行時期向操作系統申請內存,而要向操作系統申請內存,不同的操作系統提供了不同的接口,具有不同的申請內存的方式,而這主要通過需調用的函數原型不同來表現(關於函數原型,可參考《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.其表示無,即什麼都不是,這在《C++從零開始(七)》中將詳細說明。因此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的皮”就是用於修飾蘋果或梨這種類型用的,以生成一種新的類型——蘋果的果肉、梨的皮,其就相當於類型修飾符。

    本文所介紹的數組和指針都是類型修飾符,之前提過的引用變量的“&”也是類型修飾符,在《C++從零開始(七)》中將再提出幾種類型修飾符,到時也將一同說明聲明和定義這兩個重要概念,並提出聲明修飾符(decl-specifier)。

    類型修飾符只在定義變量時起作用,如前面的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個元素的內存,引用只是告知編譯器一些類型無關信息的一種手段,無法作爲類型的一種而被實例化(關於實例化,請參看《C++從零開始(十)》)。

    應該注意引用並不是類型(但出於方便,經常都將long的引用稱作一種類型),而long **&rppA = &pA;將是錯誤的,因爲上句表示的是不要給變量rppA分配內存,直接使用“=”後面的地址作爲其對應的地址,而&pA返回的並不是地址類型的數字,而是指針類型,故編譯器將報類型不匹配的錯誤。但是即使long **&rppA = pA;也同樣失敗,因爲long*和long**是不同的,不過由於類型的匹配,下面是可以的(其中的rpA2很令人疑惑,將在《C++從零開始(七)》中說明):

    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++從零開始(七)》中說明,如果本文看得很暈,後面在舉例時將會盡量說明指針的用途及用法,希望能有所幫助。

發佈了3 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章