數組與指針

1、一維數組

int array[10];
int *ap = arrary + 2;

ap這個很容易,你只要閱讀它的初始化表達式就能得到答案:array+2.另外,&array[2]也是與它對等的表達式。
*ap這個也很容易,間接訪問跟隨指針訪問它所指向的位置,也就是array[2]。你也可以這樣寫:* (array+2) .

 ap[0]“你不能這樣做,ap不是一個數組!”如果你是這樣想的,你就陷入了“其他語言不能這樣做“,這個慣性思維中了。記住C的下標引用和間接訪問表達式是一樣的。其對等的表達式是*(ap+(0)),除去括號,其結果與前一個表達式相等,因此,它的答案和上一題相同:array[2].

 ap+6 如果ap指向array[2],這個加法運算產生的指針所指向的元素是array[2]向後移動6個整數位置的元素。與它對等的表達式是arrary +8或&array[8].

 *ap+6 小心!這裏有兩個操作符,哪一個先執行呢?是間接訪問。間接訪問的結果再與6相加,所以這個表達式相當於表達式array[2}+6 .
 

*(ap+6) 括號迫使加法運算首先執行,所以我們這次得到的值是array[8]。注意這裏的間接訪問操作和下標引用操作的形式是完全一樣的。

ap[-1] 怎麼又是它?負值的下標!下標引用就是間接訪問表達式,你只要把它轉換爲那種形式並對它進行求值。ap指向第3個元素(就是那個下標值爲2的元素),所以使用偏移量一1使我們得到它的前一個元素,也就是array[1].


不完整的初始化

int vector[5] = {1,2,3,4,5,6 };

 int vector[5] = {1,2,3,4};

    在這兩種情況下,初始化值的數目和數組元素的數目並不匹配。第1個聲明是錯誤的,我們沒辦法把6個整型值裝到5個整型變量中。但是,第2個聲明卻是合法的,它爲數組的前4個元素提供了初始值,最後一個元素則初始化爲0.

     那麼,我們可不可以省略列表中間的那些值呢?
int      vector[5]  = {1,5};
編譯器只知道初始值不夠,但它無法知道缺少的是哪些值。所以,只允許省略最後幾個初始值。

2、多維數組

int  a;
1nt  b[10];
int c[6][10];
1nt d[3][6][10];

   a是個簡單的整數。接下來的那個聲明增加了一個維數,所以b就是一個向量,它包含10個整型元素。

 C只是在b的基礎上再增加一維,所以我們可以把C看作是一個包含6個元素的向量,只不過它的每個元素本身是一個包含10個整型元素的向量。換句話說,C是個一維數組的一維數組。                                           

   d也是如此:它是一個包含3個元素的數組,每個元素都是包含6個元素的數組,而這6個元素中的每一個又都是包含10個整型元素的數組。簡潔地說,d是一個3排6行10列的整型三維數組。
    理解這個視點是非常重要的,因爲它正是C實現多維數組的基礎。爲了加強這個概念,讓我們先來討論數組元素在內存中的存儲順序。

   考慮下面這個數組:
   int array[3];它包含3個元素,如下圖所示:

但現在假定你被告知這3個元素中的每一個實際上都是包含6個元素的數組,情況又將如何呢?下面是這個新的聲明:
int  array[3][6];下面是它在內存中的存儲形式:

 實線方框表示第1維的面每個元素的下標值分別是3個元素,虛線用於劃分第2維的6個元素。按照從左到右的順序,上面每個元素的下標分別是:

0,0 0,1 0,2 0,3 0,4 0,5  1,0 0,1 1,2 1,3 1,4 1,5  2,0 2,1 2,2 2,3 2,4 2,5

下面的代碼段將會打印出什麼樣的值呢?
int matrix[6][10];
int *mp;
.....                                                                                           
mp =&matrix[3][8];                                                                       

printf("frisr value is %d\n",*mp);                                                   

 printf("second value is %d\n",*++mp);                                      

printf("thrid value is %d\n",*++mp);

    很顯然,第1個被打印的值將是matrix[3] [8]的內容,但下一個被打印的又是什麼呢?存儲順序可以回答這個問題—--下一個元素將是最右邊下標首先變化的那個,也就是matrix[3][9]。再接下去又輪到誰呢?第9列可是一行中的最後一列啦。不過,根據存儲順序規定,一行存滿後就輪到下一行,所以下一個被打印的元素將是matrix[4] [0].
    這裏有一個相關的問題。matrix到底是6行10列還是10行6列?答案可能會令你大吃一驚—在某些上下文環境中,兩種答案都對。
    兩種都對?怎麼可能有兩個不同的答案呢?這個簡單,如果你根據下標把數據存放於數組中並在以後根據下標查找數組中的值,那麼不管你把第1個下標解釋爲行還是列,都不會有什麼區別。
    只要你每次都堅持使用同一種方法,這兩種解釋方法都是可行的。
    但是,把第1個下標解釋爲行或列並不會改變數組的存儲順序。如果你把第2個下標解釋爲行,把第2個下標解釋爲列,那麼當你按照存儲順序逐個訪問數組元素時,你所獲得的元素是按行排列的。

   另一方面,如果把第1個下標作爲列,那麼當你按前面的順序訪問數組元素時,你所得到的元素是按列排列的。你可以在你的程序中選擇更加合理的解釋方法。但是,你不能修改內存中數組元素的實際存儲方式。這個順序是由標準定義的。

   指向數組的指針
   下面這些聲明合法嗎?
   int vector[10],*vp=vector;
   int matrix[3 ][10],*mp=matrix;

    第1個聲明是合法的。它爲一個整型數組分配內存,並把vp聲明爲一個指向整型的指針,並把它初始化爲指向vector數組的第1個元素。vector和vP具有相同的類型:指向整型的指針.
    第2個聲明是非法的。它正確地創建了matrix數組,並把mp聲明爲一個指向整型的指針。但是,mp的初始化是不正確的,因爲matrix並不是一個指向整型的指針,而是一個指向整型數組的指針。

   數組指針
   我們應該怎樣聲明一個指向整型數組的指針的呢?

    int(*P)[10];
    這個聲明比我們以前見過的所有聲明更爲複雜,但它事實上並不是很難。你只要假定它是一個表達式並對它求值。下標引用的優先級高於間接訪問,但由於括號的存,首先執行的還是間接訪問。所以,P是個指針,但它指向什麼呢?
    接下來執行的是下標引用,所以P指向某種類型的數組。這個聲明表達式中並沒有更多的操作符,所以數組的每個元素都是整數。
    聲明並沒有直接告訴你P是什麼,但推斷它的類型並不困難—當我們對它執行間接訪問操作時,我們得到的是個數組,對該數組進行下標引用操作得到的是一個整型值。所以P是一個指向整型數組的指針。

  在聲明中加上初始化後是下面這個樣子:

   int  (*p) [10] = matrix;

  (*p)[10] 是一個數組指針,它使p指向matrix的第一行。

   

   指針數組

    int *api[10]是什麼?首先下標的優先級高於間接訪問(指針),因此它是一個指針數組。

    作爲函數參數的數組名
    當一個數組名作爲參數傳遞給一個函數時會發生什麼情況呢?你現在己經知道數組名的值就是一個指向數組第1個元素的指針,所以很容易明白此時傳遞給函數的是一份該指針的拷貝。                                             

    函數如果執行了下標引用,實際上是對這個指針執行間接訪問操作,並且通過這種間接訪問,函數可以訪問和修改調用程序的數組元素。
    現在我可以解釋c關於參數傳遞的表面上的矛盾之處.所有傳遞給函數的參數都是通過傳值方式進行的,但數組名參數的行爲卻彷彿它是通過傳址調用傳遞的。傳址調用是通過傳遞一個指向所需元素的指針,然後在函數中對該指針執行間接訪問操作實現對數據的訪問。作爲參數的數組名是個指針,下標引用實際執行的就是間接訪問。
    那麼數組的傳值調用行爲又是表現在什麼地方呢?傳遞給函數的是參數的一份拷貝(指向數組起始位置的指針的拷貝),所以函數可以自由地操作它的指針形參,而不必擔心會修改對應的作爲實參的指針。
    所以,此處並不存在矛盾:所有的參數都是通過傳值方式傳遞的。當然,如果你傳遞了一個指向某個變量的指針,而函數對該指針執行了間接訪問操作,那麼函數就可以修改那個變量。儘管初看上去並不明顯,但數組名作爲參數時所發生的正是這種情況。這個參數(指針)實際上是通過傳值方式傳遞的,函數得到的是該指針的一份拷貝,它可以被修改,但調用程序所傳遞的實參並不受影響。

     如下程序是一個簡單的函數,用於說明這些觀點。它把第2個參數中的字符串複製到第1個參
數所指向的緩衝區。調用程序的緩衝區將被修改,因爲函數對參數執行了間接訪問操作。但是,無論函數對參數(指針)如何進行修改,都不會修改調用程序的指針實參本身(但可能修改它所指向的內容)。
    注意whie語句中的*string++表達式。它取得suing所指向的那個字符,並且產生一個副作用,就是修改string,使它指向下一個字符。用這種方式修改形參並不會影響調用程序的實參,因爲只有傳遞給函數的那份拷貝進行了修改。

 

    在絕大多數表達式中,數組名的值是指向數組第1個元素的指針。這個規則只有兩個例外。sizeof返回整個數組所佔用的字節而不是一個指針所佔用的字節。單目操作符&返回一個指向數組的指針,而不是一個指向數組第1個元素的指針的指針。
    除了優先級不同以外,下標表達式array[value]和間接訪問表達式*(array+(value))是一樣的。因此,下標不僅可以用於數組名,也可以用於指針表達式中。不過這樣一來,編譯器就很難檢查下標的有效性。指針表達式可能比下標表達式效率更高,但下標表達式絕不可能比指針表達式效率更高。
    指針和數組並不相等。數組的屬性和指針的屬性大相徑庭。當我們聲明一個數組時,它同時也分配了一些內存空間,用於容納數組元素。但是當我們聲明一個指針時,它只分配了用於容納指針本身的空間。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章