日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

C語言的指針與多維數組

 

指針和多維數組有什么關系?為什么要了解它們的關系?處理多維數組的函數要用到指針,所以在使用這種函數之前,先要更深入地學習指針。至于第1個問題,我們通過幾個示例來回答。為簡化討論,我們使用較小的數組。假設有下面的聲明:

int zippo[4][2];  /* an array of arrays of ints */

然后數組名zippo是該數組首元素的地址。在本例中,zippo的首元素是一個內含兩個int值的數組,所以zippo是這個內含兩個int值的數組的地址。下面,我們從指針的屬性進一步分析。

  • 因為zippo是數組首元素的地址,所以zippo的值和&zippo[0]的值相同。而zippo[0]本身是一個內含兩個整數的數組,所以zippo[0]的值和它首元素(一個整數)的地址(即&zippo[0][0]的值)相同。簡而言之,zippo[0]是一個占用一個int大小對象的地址,而zippo是一個占用兩個int大小對象的地址。由于這個整數和內含兩個整數的數組都開始于同一個地址,所以zippo和zippo[0]的值相同。
  • 給指針或地址加1,其值會增加對應類型大小的數值。在這方面,zippo和zippo[0]不同,因為zippo指向的對象占用了兩個int大小,而zippo[0]指向的對象只占用一個int大小。因此,zippo + 1和zippo[0] + 1的值不同。
  • 解引用一個指針(在指針前使用*運算符)或在數組名后使用帶下標的[]運算符,得到引用對象代表的值。因為zippo[0]是該數組首元素(zippo[0][0])的地址,所以*(zippo[0])表示存儲在zippo[0][0]上的值(即一個int類型的值)。與此類似,*zippo代表該數組首元素(zippo[0])的值,但是zippo[0]本身是一個int類型值的地址。該值的地址是&zippo[0][0],所以*zippo就是&zippo[0][0]。對兩個表達式應用解引用運算符表明,**zippo與*&zippo[0][0]等價,這相當于zippo[0][0],即一個int類型的值。簡而言之,zippo是地址的地址,必須解引用兩次才能獲得原始值。地址的地址或指針的指針是就是雙重間接(double indirection)的例子。

顯然,增加數組維數會增加指針的復雜度。現在,大部分初學者都開始意識到指針為什么是C語言中最難的部分。認真思考上述內容,看看是否能用所學的知識解釋程序中的程序。該程序顯示了一些地址值和數組的內容zippo1.c。

/* zippo1.c --  zippo info */
#include <stdio.h>
int main(void)
{
    int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5, 7} };

    printf("zippo = %p, zippo + 1 = %pn",
               zippo,         zippo + 1);
    printf("zippo[0] = %p, zippo[0] + 1 = %pn",
            zippo[0],      zippo[0] + 1);
    printf("*zippo = %p,   *zippo + 1 = %pn",
              *zippo,        *zippo + 1);
    printf("zippo[0][0] = %dn", zippo[0][0]);
    printf("  *zippo[0] = %dn", *zippo[0]);
    printf("    **zippo = %dn", **zippo);
    printf("      zippo[2][1] = %dn", zippo[2][1]);
    printf("*(*(zippo+2) + 1) = %dn", *(*(zippo+2) + 1));

    return 0;
}

下面是我們的系統運行該程序后的輸出:

: zippo = 0x7fff26a9a3a0, zippo + 1 = 0x7fff26a9a3a8
: zippo[0] = 0x7fff26a9a3a0, zippo[0] + 1 = 0x7fff26a9a3a4
: *zippo = 0x7fff26a9a3a0,   *zippo + 1 = 0x7fff26a9a3a4
: zippo[0][0] = 2
:   *zippo[0] = 2
:     **zippo = 2
:       zippo[2][1] = 3
: *(*(zippo+2) + 1) = 3

其他系統顯示的地址值和地址形式可能不同,但是地址之間的關系與以上輸出相同。該輸出顯示了二維數組zippo的地址和一維數組zippo[0]的地址相同。它們的地址都是各自數組首元素的地址,因而與&zippo[0][0]的值也相同。

盡管如此,它們也有差別。在我們的系統中,int是4字節。前面討論過,zippo[0]指向一個4字節的數據對象。zippo[0]加1,其值加4(十六進制中,38+4得3c)。數組名zippo是一個內含2個int類型值的數組的地址,所以zippo指向一個8字節的數據對象。因此,zippo加1,它所指向的地址加8字節(十六進制中,38+8得40)。

該程序演示了zippo[0]和*zippo完全相同,實際上確實如此。然后,對二維數組名解引用兩次,得到存儲在數組中的值。使用兩個間接運算符(*)或者使用兩對方括號([])都能獲得該值(還可以使用一個*和一對[],但是我們暫不討論這么多情況)。

要特別注意,與zippo[2][1]等價的指針表示法是*(*(zippo+2) + 1)。看上去比較復雜,應最好能理解。下面列出了理解該表達式的思路:

C語言的指針與多維數組

 

以上分析并不是為了說明用指針表示法(*(*(zippo+2) + 1))代替數組表示法(zippo[2][1]),而是提示讀者,如果程序恰巧使用一個指向二維數組的指針,而且要通過該指針獲取值時,最好用簡單的數組表示法,而不是指針表示法。

下圖以另一種視圖演示了數組地址、數組內容和指針之間的關系。

C語言的指針與多維數組

An array of arrays.

指向多維數組的指針

如何聲明一個指針變量pz指向一個二維數組(如,zippo)?在編寫處理類似zippo這樣的二維數組時會用到這樣的指針。把指針聲明為指向int的類型還不夠。因為指向int只能與zippo[0]的類型匹配,說明該指針指向一個int類型的值。但是zippo是它首元素的地址,該元素是一個內含兩個int類型值的一維數組。因此,pz必須指向一個內含兩個int類型值的數組,而不是指向一個int類型值,其聲明如下:

 int (* pz)[2];  // pz points to an array of 2 ints

以上代碼把pz聲明為指向一個數組的指針,該數組內含兩個int類型值。為什么要在聲明中使用圓括號?因為[]的優先級高于*。考慮下面的聲明:

int * pax[2];  // pax is an array of two pointers-to-int

由于[]優先級高,先與pax結合,所以pax成為一個內含兩個元素的數組。然后*表示pax數組內含兩個指針。最后,int表示pax數組中的指針都指向int類型的值。因此,這行代碼聲明了兩個指向int的指針。而前面有圓括號的版本,*先與pz結合,因此聲明的是一個指向數組(內含兩個int類型的值)的指針。程序zippo2.c演示了如何使用指向二維數組的指針。

/* zippo2.c --  zippo info via a pointer variable */
#include <stdio.h>
int main(void)
{
    int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5, 7} };
    int (*pz)[2];
    pz = zippo;

    printf("   pz = %p,    pz + 1 = %pn",
               pz,         pz + 1);
    printf("pz[0] = %p, pz[0] + 1 = %pn",
            pz[0],      pz[0] + 1);
    printf("  *pz = %p,   *pz + 1 = %pn",
              *pz,        *pz + 1);
    printf("pz[0][0] = %dn", pz[0][0]);
    printf("  *pz[0] = %dn", *pz[0]);
    printf("    **pz = %dn", **pz);
    printf("      pz[2][1] = %dn", pz[2][1]);
    printf("*(*(pz+2) + 1) = %dn", *(*(pz+2) + 1));

    return 0;
}

下面是該程序的輸出:

: pz = 0x7ffc3a82bc60,    pz + 1 = 0x7ffc3a82bc68
: pz[0] = 0x7ffc3a82bc60, pz[0] + 1 = 0x7ffc3a82bc64
:   *pz = 0x7ffc3a82bc60,   *pz + 1 = 0x7ffc3a82bc64
: pz[0][0] = 2
:   *pz[0] = 2
:     **pz = 2
:       pz[2][1] = 3
: *(*(pz+2) + 1) = 3

系統不同,輸出的地址可能不同,但是地址之間的關系相同。如前所述,雖然pz是一個指針,不是數組名,但是也可以使用pz[2][1]這樣的寫法。可以用數組表示法或指針表示法來表示一個數組元素,既可以使用數組名,也可以使用指針名:

: zippo[m][n] == *(*(zippo + m) + n)
: pz[m][n] == *(*(pz + m) + n)

指針的兼容性

指針之間的賦值比數值類型之間的賦值要嚴格。例如,不用類型轉換就可以把int類型的值賦給double類型的變量,但是兩個類型的指針不能這樣做。

int n = 5;
double x;
int * p1 = &n;
double * pd    = &x;
x = n;                // implicit type conversion
pd = p1;              // compile-time error

更復雜的類型也是如此。假設有如下聲明:

int * pt;
int (*pa)[3];
int ar1[2][3];
int ar2[3][2];
int **p2;       // a pointer to a pointer

有如下的語句:

pt = &ar1[0][0];  // both pointer-to-int
pt = ar1[0];      // both pointer-to-int
pt = ar1;         // not valid
pa = ar1;         // both pointer-to-int[3]
pa = ar2;         // not valid
p2 = &pt;         // both pointer-to-int *
*p2 = ar2[0];     // both pointer-to-int
p2 = ar2;         // not valid

注意,以上無效的賦值表達式語句中涉及的兩個指針都是指向不同的類型。例如,pt指向一個int類型值,而ar1指向一個內含3個int類型元素的數組。類似地,pa指向一個內含3個int類型元素的數組,所以它與ar1的類型兼容,但是ar2指向一個內含2個int類型元素的數組,所以pa與ar2不兼容。

上面的最后兩個例子有些棘手。變量p2是指向指針的指針,它指向的指針指向int,而ar2是指向數組的指針,該數組內含2個int類型的元素。所以,p2和ar2的類型不同,不能把ar2賦給p2。但是,*p2是指向int的指針,與ar2[0]兼容。因為ar2[0]是指向該數組首元素(ar2[0][0])的指針,所以ar2[0]也是指向int的指針。

一般而言,多重解引用讓人費解。例如,考慮下面的代碼:

int x = 20;
const int y = 23;
int * p1 = &x;
const int * p2 = &y;
const int ** pp2;
p1 = p2;   // not safe -- assigning const to non-const
p2 = p1;   // valid    -- assigning non-const to const
pp2 = &p1; // not safe -- assigning nested pointer types

前面提到過,把const指針賦給非const指針不安全,因為這樣可以使用新的指針改變const指針指向的數據。編譯器在編譯代碼時,可能會給出警告,執行這樣的代碼是未定義的。但是把非const指針賦給const指針沒問題,前提是只進行一級解引用:

p2 = p1;   // valid     -- assigning non-const to const

但是進行兩級解引用時,這樣的賦值也不安全,例如,考慮下面的代碼:

const int **pp2;
int *p1;
const int n = 13;
pp2 = &p1; // allowed, but const qualifier disregarded
*pp2 = &n; // valid, both const, but sets p1 to point at n
*p1 = 10;  // valid, but tries to change const n

發生了什么?如前所示,標準規定了通過非const指針更改const數據是未定義的。例如,在Terminal中(OS X對底層UNIX系統的訪問)使用gcc編譯包含以上代碼的小程序,導致n最終的值是13,但是在相同系統下使用clang來編譯,n最終的值是10。兩個編譯器都給出指針類型不兼容的警告。當然,可以忽略這些警告,但是最好不要相信該程序運行的結果,這些結果都是未定義的。

C const和C++ const

C和C++中const的用法很相似,但是并不完全相同。區別之一是,C++允許在聲明數組大小時使用const整數,而C卻不允許。區別之二是,C++的指針賦值檢查更嚴格:

const int y;
const int * p2 = &y;
int * p1;
p1 = p2;   // error in C++, possible warning in C

C++不允許把const指針賦給非const指針。而C則允許這樣做,但是如果通過p1更改y,其行為是未定義的。

函數和多維數組

如果要編寫處理二維數組的函數,首先要能正確地理解指針才能寫出聲明函數的形參。在函數體中,通常使用數組表示法進行相關操作。下面,我們編寫一個處理二維數組的函數。一種方法是,利用for循環把處理一維數組的函數應用到二維數組的每一行。如下所示:

int junk[3][4] = { {2,4,5,8}, {3,5,6,9}, {12,10,8,6} };
int i, j;
int total = 0;
for (i = 0; i < 3 ; i++)
    total += sum(junk[i], 4);  // junk[i] -- one-dimensional array

記住,如果junk是二維數組,junk[i]就是一維數組,可將其視為二維數組的一行。這里,sum()函數計算二維數組的每行的總和,然后for循環再把每行的總和加起來。

然而,這種方法無法記錄行和列的信息。用這種方法計算總和,行和列的信息并不重要。但如果每行代表一年,每列代表一個月,就還需要一個函數計算某列的總和。該函數要知道行和列的信息,可以通過聲明正確類型的形參變量來完成,以便函數能正確地傳遞數組。在這種情況下,數組junk是一個內含3個數組元素的數組,每個元素是內含4個int類型值的數組(即junk是一個3行4列的二維數組)。通過前面的討論可知,這表明junk是一個指向數組(內含4個int類型值)的指針。可以這樣聲明函數的形參:

void somefunction( int (* pt)[4] );

另外,如果當且僅當pt是一個函數的形式參數時,可以這樣聲明:

void somefunction( int pt[][4] );

注意,第1個方括號是空的。空的方括號表明pt是一個指針。這樣的變量稍后能以同樣的方式用作junk。下面的程序示例中就是這樣做的,如程序array2d.c所示。注意該程序清單演示了3種等價的原型語法。

// array2d.c -- functions for 2d arrays
#include <stdio.h>
#define ROWS 3
#define COLS 4
void sum_rows(int ar[][COLS], int rows);
void sum_cols(int [][COLS], int );    // ok to omit names
int sum2d(int (*ar)[COLS], int rows); // another syntax
int main(void)
{
     int junk[ROWS][COLS] = {
            {2,4,6,8},
            {3,5,7,9},
            {12,10,8,6}
     };

     sum_rows(junk, ROWS);
     sum_cols(junk, ROWS);
     printf("Sum of all elements = %dn", sum2d(junk, ROWS));

     return 0;
}

void sum_rows(int ar[][COLS], int rows)
{
    int r;
    int c;
    int tot;

    for (r = 0; r < rows; r++)
    {
        tot = 0;
        for (c = 0; c < COLS; c++)
            tot += ar[r][c];
        printf("row %d: sum = %dn", r, tot);
    }
}

void sum_cols(int ar[][COLS], int rows)
{
    int r;
    int c;
    int tot;

    for (c = 0; c < COLS; c++)
    {
        tot = 0;
        for (r = 0; r < rows; r++)
            tot += ar[r][c];
        printf("col %d: sum = %dn", c, tot);
    }
}

int sum2d(int ar[][COLS], int rows)
{
    int r;
    int c;
    int tot = 0;

    for (r = 0; r < rows; r++)
        for (c = 0; c < COLS; c++)
            tot += ar[r][c];

    return tot;
}

程序array2d.c中的程序把數組名junk(即,指向數組首元素的指針,首元素是子數組)和符號常量ROWS(代表行數3)作為參數傳遞給函數。每個函數都把ar視為內含數組元素(每個元素是內含4個int類型值的數組)的數組。列數內置在函數體中,但是行數靠函數傳遞得到。如果傳入函數的行數是12,那么函數要處理的是12×4的數組。因為rows是元素的個數,然而,因為每個元素都是數組,或者視為一行,rows也可以看成是行數。

注意,ar和main()中的junk都使用數組表示法。因為ar和junk的類型相同,它們都是指向內含4個int類型值的數組的指針。

注意,下面的聲明不正確:

int sum2(int ar[][], int rows); // faulty declaration

前面介紹過,編譯器會把數組表示法轉換成指針表示法。例如,編譯器會把ar[1]轉換成ar+1。編譯器對ar+1求值,要知道ar所指向的對象大小。下面的聲明:

int sum2(int ar[][4], int rows); // valid declaration

表示ar指向一個內含4個int類型值的數組(在我們的系統中,ar指向的對象占16字節),所以ar+1的意思是“該地址加上16字節”。如果第2對方括號是空的,編譯器就不知道該怎樣處理。也可以在第1對方括號中寫上大小,如下所示,但是編譯器會忽略該值:

int sum2(int ar[3][4], int rows); // valid declaration, 3 ignored

與使用typedef相比,這種形式方便得多:

typedef int arr4[4];              // arr4 array of 4 int
typedef arr4 arr3x4[3];           // arr3x4 array of 3 arr4
int sum2(arr3x4 ar, int rows);    // same as next declaration
int sum2(int ar[3][4], int rows); // same as next declaration
int sum2(int ar[][4], int rows);  // standard form

一般而言,聲明一個指向N維數組的指針時,只能省略最左邊方括號中的值

int sum4d(int ar[][12][20][30], int rows);

因為第1對方括號只用于表明這是一個指針,而其他的方括號則用于描述指針所指向數據對象的類型。下面的聲明與該聲明等價:

int sum4d(int (*ar)[12][20][30], int rows);  // ar a pointer

這里,ar指向一個12×20×30的int數組。

分享到:
標簽:指針 語言
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定