3 指標與陣列 3-1 指標與一維陣列 3-2 指標與二維陣列 3-3 陣列指標 3-4 為什麼 parr 等同於 *parr? 3-5 指向陣列的指標 3-6 多重指標 3-7 命令列引數 3-8 除錯題 3-9 問題演練 3-10 程式實作
32 Part 1 C 程式語言篇 指標其實就是一位址 陣列的名稱, 表示此陣列第一個元素的位址, 所以它也是指標 由此可知, 指標與陣列的關係是很密切的 為了與指標變數 (pointer variable) 有所區別, 我們稱陣列名稱為是一指標常數 (pointer constant) 指標變數與指標常數的不同處是, 前者可以使用遞增運算子 (++), 或遞減運算子 (--) 來遞增和遞減指標, 但後者不行 因此, 若在程式中使用 arr++ 或 arr--, 則會出現錯誤的訊息 3-1 指標與一維陣列 我們先來看指標與一維陣列 (one dimension) 的關係 請參閱範例 pointerarr1-5 範例 pointerarr1-5 /* pointerarr1-5.c */ #include <conio.h> int arr[]= 100, 101, 102; int *ptr = arr; int i, size = 0; size = (sizeof arr/ sizeof (arr[0]) ); /* ------------Using arr--------------*/ printf(" 使用 arr 指標常數來表示 :\n"); printf("&arr[%d] = %x\n", i, &arr[i]); printf("arr+%d = %x\n", i, arr+i); printf("arr[%d] = %x\n", i, arr[i]); printf("*(arr+%d) = %x\n", i, *(arr+i)); /*--------------Using ptr-------------*/ printf("\n 使用 ptr 指標變數來表示 :\n");
Chapter 3 指標與陣列 33 printf("ptr+%d = %x\n", i, ptr+i); printf("ptr[%d] = %d\n", i, ptr[i]); printf("*(ptr+%d) = %d\n", i, *(ptr+i)); getch(); 從得知,arr 是陣列名稱, 它是指標常數, 而 ptr 是指標變數 arr 表示此陣列第一個元素的位址, 亦即 arr 等同於 &arr[0]
34 Part 1 C 程式語言篇 arr 可以使用指標變數的 * 表示符號, 如 *arr 等同於 arr[0],*(arr+1) 等同於 arr[1], 依此類推 同理,ptr 也可以使用指標常數的 [] 表示符號, 如目前 ptr 所指向變數位址的值為 ptr[0], 其等同於 *ptr, 而 ptr[1] 等同於 *(ptr+1), 依此類推 再來看範例 pionterarr1-10 範例 pointerarr1-10 /* pointerarr1-10.c */ #include <conio.h> int i[] = 100, 200, 300, 400, 500; int *ptr = i+2; int k; printf("ptr[-2]=%d\n", ptr[-2]); printf("ptr[-1]=%d\n", ptr[-1]); printf("ptr[0]=%d\n", ptr[0]); printf("ptr[1]=%d\n", ptr[1]); printf("ptr[2]=%d\n\n", ptr[2]); ptr++; printf("after executing ptr++...\n"); printf("ptr[0]=%d\n", ptr[0]); printf("*(ptr+0)=%d\n", *(ptr+0)); printf("ptr[1]=%d\n", ptr[1]); printf("*(ptr+1)=%d\n", *(ptr+1)); getch();
Chapter 3 指標與陣列 35 ptr 與 i 陣列的關係圖, 如下所示 : ptr:i+2 i i+1 i+2 i+3 i+4 i[0]:100 i[1]:200 i[2]:300 i[3]:400 i[4]:500 程式一開始將 ptr 指向 i+2( 它是 i[2] 的位址 ), 所以 ptr[0] 等於 i[2], 因為 ptr[0] 表示目前 ptr 所指向變數位址的值 同時也得知 ptr[-1] 是 i[1] ptr[-2] 是 i[0] ptr[1] 是 i[3] ptr[2] 是 i[4] 我們從此範例得到以下的公式 ptr[i] == *(ptr+i) 大部份的使用者都是以 *(ptr+i), 間接存取陣列中索引為 i 的元素值 當指標與 ++ 遞增運算子一起運算時, 必須注意 ++ 的作用點在那裏, 是對位址加 1, 或是將變數值加 1 若是對位址加 1, 則將指標移到下一元素的位址 請參閱範例 pointer&++ 範例 pointer&++ /* pointer&++.c */ #include <conio.h> int i[] = 100, 200, 300, 400, 500; int *pi = i; printf("i=%p, pi=%p\n", i, pi); printf("i[0]=%d\n", i[0]); printf("*pi=%d\n\n", *pi); pi+1; printf("after pi+1, pi=%p\n", pi);; printf("*pi=%d\n\n", *pi); pi++; printf("after pi++, pi=%p\n", pi); printf("*pi =%d\n", *pi); getch();
36 Part 1 C 程式語言篇 i=0022ff50, pi=0022ff50 i[0]=100 *pi=100 After pi+1, pi=0022ff50 *pi=100 After pi++, pi=0022ff54 *pi=200 從得知,pi+1; 只是將目前的 pi 往下移到下一個元素的位址, 它並沒有覆蓋 pi, 而 pi++; 不僅將目前的 pi 移到下一個元素的位址, 而且還將此新值覆蓋 pi 我們可以對指標變數 pi 做 ++ 的動作, 但不可以對陣列名稱 i 做 ++ 的動作 當指標 遞增 (++) 運算子或遞減 (--) 運算子, 及 * 這三個運算子在同一敘述時, 要注意 ++ 的作用點在那裏 請參閱範例 pointer&++2 範例 pointer&++2 /* pointer&++2.c */ #include <conio.h> int i[] = 100, 200, 300, 400, 500; int *pi = i; printf("...%d\n", *pi++); printf("*pi = %d\n", *pi); printf("...%d\n", *++pi); printf("*pi = %d\n", *pi); printf("...%d\n", ++*pi); printf("*pi = %d\n", *pi); getch(); 100 *pi = 200 300 *pi=300 301 *pi = 301
Chapter 3 指標與陣列 37 從程式定義中 int i[ ] = 100, 200, 300, 400, 500; int *pi = i; 得知其示意圖如下 : pi i i+1 i+2 i+3 i+4 100 200 300 400 500 下一敘述 *pi++; 當 * 和 ++ 在同一敘述時, 要注意 ++ 的作用是, 對位址加 1 或對值加 1 若對位址加 1, 表示將 pi 指標移到下一位址 由於 * 和 ++ 的運算優先順序相同, 且其結合性是由右至左, 因此 *pi++ 其實就是 *(pi++);, 但此處的 ++ 為後繼加, 所以先得到 *pi 為 100 之後, 才會處理 ++ 的動作 以此敘述先印出 100, 再將 pi 指向下一位址 如下圖所示 : pi i i+1 i+2 i+3 i+4 100 200 300 400 500 接下來的 *++pi; 由於是此敘述相當於 *(++pi), 此處的 ++ 是前置加, 所以 pi 指標先移到了下一位址, 再印出 *pi 的值 (300), 如下圖所示 : pi i i+1 i+2 i+3 i+4 100 200 300 400 500 最後 ++*pi; 此敘述相當於 ++(*pi), 由此可知,++ 乃針對 *pi 的值加 1, 此敘述等同於
38 Part 1 C 程式語言篇 *pi = *pi + 1; 將 *pi(= 300) 加 1, 再放入 *pi 中, 如下圖所示 : pi i i+1 i+2 i+3 i+4 100 200 301 400 500 最後,*pi 的運算結果為 301 3-2 指標與二維陣列 一維陣列與指標的關係, 由上述可得知, 一維陣列的元素值, 可利用 [] 或一個 * 得到, 如有一陣列如下 : int i[7] = 0, 1, 2, 3, 4, 5, 6; int *ptr=i; 則 *(i+2) 或 i[2] 或 *(ptr+2) 或 ptr[2], 皆表示陣列某一元素的值 而二維陣列 (two dimension), 則需兩個 *, 或一個 * 一個 [ ], 或兩個 [ ], 才可得到陣列的元素值, 其餘的表示法, 只能得到陣列元素的位址 假設有一個二維陣列的定義如下 : int j[2][3] = 0, 1, 2, 3, 4, 5; j 0 1 2 j+1 3 4 5 其中 j 是此陣列的名稱, 表示此陣列第一列第一行元素之位址, 而 j+1 是第二列第一行元素的位址, 除此之外 j[0] 0 1 2 j[1] 3 4 5 j[0] 和 j[1] 的意思等同於 j 和 j+1, 表示第一列第一個元素和第二列第一個元素的位址 j 和 j[0] 雖然表示同一元素的位址, 但兩者加 1 個單位的意思是不相同的
Chapter 3 指標與陣列 39 j+1 表示第二列第一個元素的位址, 即 j+1==&j[1][0]; 而 j[0]+1 是第一列 第二個元素的位址, 即 j[0]+1==&j[0][1], 如下圖所示 j[0]+1 j[0] 0 1 2 j[1] 3 4 5 有關二維陣列的每一元素所在記憶體的觀念, 已在第一章討論過, 請參閱範例 address2array-5 和 address2array-10 接下來, 試問 *j 和 *(j+1) 是某一元素的位址, 還是某一元素的值呢? 答案是某一元素的位址 j 與 *j 皆表示第一列第一行元素的位址 (&j[0][0]) 而 j+1 與 *(j+1) 皆表示第二列第一行元素的位址 (&j[1][0]), 如下圖所示 : *j, j 0 1 2 *(j+1), j+1 3 4 5 雖然 j 與 *j 表示相同的意思, 但兩者各加 1, 表示的意義是不相同的 j+1 表示第二列第一行的位址, 而 *j+1 表示第一列第二行的位址 (&j[0][1]) 同理,*(j+1)+1 是第二列第二行的位址 (&j[1][1]) 請參閱範例 pointerarr2-5 範例 pointerarr2-5 /* pointerarr2-5 */ #include <conio.h> int j[2][3] = 10, 20, 30, 40, 50, 60; int k; for(k=0; k<3; k++) printf("j[%d] = %p\n", k, j[k]); for(k=0; k<3; k++) printf("j+%d = %p\n", k, j+k ); for(k=0; k<3; k++) printf("*(j+%d) = %p\n", k, *(j+k)); getch();
40 Part 1 C 程式語言篇 j[0] = 0022FF50 j[1] = 0022FF5c j[2] = 0022FF68 j+0 = 0022FF50 j+1 = 0022FF5c j+2 = 0022FF68 *(j+0) = 0022FF50 *(j+1) = 0022FF5c *(j+2) = 0022FF68 綜合上述, 若有一個二維陣列 k, 如下所示 : int k[3][4] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11; 陣列的示意圖, 如下圖所示 : 0 1 2 3 4 5 6 7 8 9 10 11 如何得到元素值 6( 位於第二列第三個元素 ) 的位址呢? 答案如下 : &k[1][2],k[1]+2,*(k+1)+2 其中 &k[1][2] 很清楚的可以看出, 它表示 k[1][2] 元素的位址 *(k+1) 和 k[1] 皆表示第二列第一個元素的位址, 所以再加 2, 皆可得到第二列第三個元素的位址 當我們得到元素的位址後, 再加上一個 * 就可以得到該元素的值, 所以 k[1][2],*(k[1]+2),*(*(k+1)+2) 都可以得到陣列第二列第三行的值 由以上的敘述, 可以導出下一公式 : k[x][y] == *(k[x]+y) == *(*(k+x)+y) 這一公式是可以很容易理解的, 因為 * 和 [] 是互通的 請參閱範例 pointerarr2-10
Chapter 3 指標與陣列 41 範例 pointerarr2-10 /* pointerarr2-10.c */ #include <conio.h> int k[3][4] = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12; printf("&k[1][2] = %p\n", &k[1][2]); printf("k[1][2] = %d\n", k[1][2]); printf("*(k + 1) + 2 = %p\n", *(k + 1) + 2); printf("*(*(k + 1) + 2) = %d\n", *(*(k + 1) + 2)); printf("k[1] + 2 = %p\n", k[1] + 2); printf("*(k[1] + 2) = %d\n", *(k[1] + 2)); getch(); 範例程式使用三種方式, 得到 k[1][2] 的位址與數值 要得到 k[1][2] 的值 7, 可以使用 *(k[1]+2) 及 *(*(k + 1) + 2) 若要得到 k[1][2] 的位址, 則可使用 &k[1][2] k[1] + 2 及 *(k + 1) + 2, 此處以 %p 印出陣列第二列第三個元素的位址是 0022FF58 3-3 陣列指標 陣列指標 (pointers of array) 表示陣列的元素, 皆指向某一資料型態的指標, 如 char *parr[4] = Department, of, Information, Management ;
42 Part 1 C 程式語言篇 由於 [ ] 運算子運算優先順序高於 * 運算子, 因此,parr 是 4 個元素組成的陣列, 陣列中的每一元素皆是指向 char 資料型態的指標 若將上述的定義以圖形加以輔助的話, 則可以很快的得到答案 此敘述的陣列名稱 parr, 表示第一個元素 parr[0] 的位址, 而且是一指標常數, 所以 *parr 是 parr[0], 而 parr[0] 是 Department 字串中 D 字元的位址 parr 可視為二維陣列 ( 因為指標 (*) 和陣列 ([]) 是互通 ), 我們可利用下列三種方式 : 1. 兩個 *, 如 **parr 2. 一個 *, 搭配一個 [ ], 如 *parr[0] 3. 兩個 [ ], 如 parr[0][0] 印出 Department 字串中的 D 字元 除了上述三種方式外, 其餘的表示法將得到位址而已 請參閱範例 pointerofarray-5
Chapter 3 指標與陣列 43 範例 pointerofarray-5 /* pointerofarray-5.c */ #include <stdlib.h> char *parr[] = "Department", "of", "Information", "Management"; printf("*parr[3] = %c\n", *parr[3]); printf("**(parr+3) = %c\n", **(parr+3)); printf("parr[3][0] = %c\n", parr[3][0]); printf("parr[2] = %s\n", parr[2]); printf("*(parr+2) = %s\n", *(parr+2)); system( PAUSE ); *parr[3] = M **(parr+3) = M parr[3][0] = M parr[2] = Information *(parr+2) = Information 從程式得知, 要印出某一字串的字元, 則可利用 *parr[3] **(parr+3) parr[3][0] 這三種方式印出, 並以 %c 為其格式 ( 因為 parr 的每一元素皆為指向 char 的指標 ), 而列印字串只需知道字串的第一個字元的位址即可, 並以 %s 格式輸出, 如 parr[2] 和 *(parr+2) 皆為指向某一字元的位址 承上例, 若欲列印某字串中的子字串, 如 Department 字串中的 ment, 則可利用下列敘述之一加以輸出 1. printf( %s, parr[0]+6); 2. printf( %s, *parr+6);