在宣告陣列之後,使用到陣列變數時,會取得首元素的位址,例如在下面的程式中將指出,陣列 arr
與 &arr[0]
的值是相同的:
#include <stdio.h>
int main(void) {
int arr[10] = {0};
printf("arr :\t\t%p\n", arr);
printf("&arr[0] :\t%p\n", &arr[0]);
return 0;
}
執行結果:
arr : 0061FEA8
&arr[0] : 0061FEA8
之前也曾經談過,陣列索引其實是相對於首元素位址的位移量,下面這個程式以指標運算與陣列索引操作,顯示出相同的對應位址值:
#include <stdio.h>
#define LEN 10
int main(void) {
int arr[LEN] = {0};
int *p = arr;
for(int i = 0; i < LEN; i++) {
printf("&arr[%d]: %p", i ,&arr[i]);
printf("\t\tptr + %d: %p\n", i, p + i);
}
return 0;
}
每個元素的位址型態是 int*
,執行結果如下:
&arr[0]: 0061FEA0 ptr + 0: 0061FEA0
&arr[1]: 0061FEA4 ptr + 1: 0061FEA4
&arr[2]: 0061FEA8 ptr + 2: 0061FEA8
&arr[3]: 0061FEAC ptr + 3: 0061FEAC
&arr[4]: 0061FEB0 ptr + 4: 0061FEB0
&arr[5]: 0061FEB4 ptr + 5: 0061FEB4
&arr[6]: 0061FEB8 ptr + 6: 0061FEB8
&arr[7]: 0061FEBC ptr + 7: 0061FEBC
&arr[8]: 0061FEC0 ptr + 8: 0061FEC0
&arr[9]: 0061FEC4 ptr + 9: 0061FEC4
在這個程式中,將陣列的首元素位址指定給 p
,然後對 p
遞增運算,每遞增一個單位,陣列相對應索引的元素之位址都相同。
也可以利用指標運算來取出陣列的元素值,如以下的程式所示:
#include <stdio.h>
#define LEN 5
int main(void) {
int arr[LEN] = {10, 20, 30, 40, 50};
int *p = arr;
// 以指標方式存取
for(int i = 0; i < LEN; i++) {
printf("*(p + %d): %d\n", i , *(p + i));
}
putchar('\n');
// 以指標方式存取資料
for(int i = 0; i < LEN; i++) {
printf("*(arr + %d): %d\n", i , *(arr + i));
}
return 0;
}
執行結果:
*(p + 0): 10
*(p + 1): 20
*(p + 2): 30
*(p + 3): 40
*(p + 4): 50
*(arr + 0): 10
*(arr + 1): 20
*(arr + 2): 30
*(arr + 3): 40
*(arr + 4): 50
在上面的程式中,可以使用指標運算配合 *
運算子來取出陣列中的每個元素,也可以配合下標運算子來取出陣列元素。
在〈陣列〉中談過,可以使用 sizeof
來計算陣列長度,在認識指標及其運算後,透過以下也可以計算出陣列長度:
int arr[] = {10, 20, 30, 40, 50};
int len = *(&arr + 1) - arr;
來解釋一下為什麼這行得通,如果使用 &arr
會取得 arr
變數的位址值,也就是陣列資料儲存的位址,與首元素位址是相同的值:
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
printf("%p\n", arr); // 顯示 0061FEBC
printf("%p\n", &arr); // 顯示 0061FEBC
return 0;
}
每個陣列元素的位址型態是 int*
,這表示對它進行運算時,是以 int
長度為單位,而 arr
變數的位址處就是陣列資料的開端,&arr
型態會是…呃…int (*)[5]
,5 是陣列長度,如果想宣告相對應的變數,可以如下:
int (*p)[5] = &arr;
int (*)[5]
表示,對它進行運算時,是以 5 個 int
長度為單位,因此 &arr + 1
的結果,會是陣列使用的空間後之位址,而 *(&arr + 1)
的值型態會回到 int*
,也就是最後一個元素後之位址,這時就可以與 int*
的 arr
進行相減,也就是與第一個元素之位址相減,就可以得到陣列長度了。
舉這個例子的重點之一是,對於同一個位址,指標的型態決定了該怎麼看得相對應相加、相減計算;另一個重點是,透過陣列變數會取得首元素的位址,將陣列變數指定給指標 p
,就只是取得首元素位址並儲存在 p
,如果將 p
傳給 sizeof
,那使用的會是指標 p
的型態,而不是原陣列的型態,這會令 sizeof
、以及方才那神奇計算長度的方式失效,例如:
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%p\n", p); // 顯示 0061FEBC
printf("%p\n", &p); // 顯示 0061FEB8
printf("%d\n", sizeof(p)/sizeof(*p)); // 顯示 1
printf("%d\n", *(&p + 1) - p); // 顯示 -1605549
return 0;
}
C++ 11 提供了 begin
與 end
函式,可以計算陣列長度:
constexpr int LENGTH = 5;
int arr[LENGTH] = {10, 20, 30, 40, 50};
cout << end(arr) - begin(arr) << endl; // 顯示 5
在 C 語言中,可以這麼做:
int arr[] = {10, 20, 30, 40, 50};
int *begin = arr;
int *end = *(&arr + 1);
printf("%d\n", end - begin);
因此基於指標,也可以使用以下的風格來迭代陣列,而不是使用索引:
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
int *begin = arr;
int *end = *(&arr + 1);
for(int *it = begin; it < end; it++) {
printf("%d ", *it);
}
return 0;
}
在〈二維(多維)陣列〉中談過,C 沒有二維陣列這種東西,二維或多維陣列的概念,是以陣列的陣列(arrays of arrays)來實現,例如,底下可以分別求得 maze
的列數與每列的長度:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
printf("ROWS: %d\n", sizeof(maze) / sizeof(maze[0]));
printf("LEN: %d\n", sizeof(maze[0]) / sizeof(maze[0][0]));
return 0;
}
或者是使用以下程式:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
printf("ROWS: %d\n", *(&maze + 1) - maze);
printf("LEN: %d\n", *(&maze[0] + 1) - maze[0]);
return 0;
}
執行結果都是:
ROWS: 2
LEN: 3
〈二維(多維)陣列〉也曾經舉了個例子:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
for(int i = 0; i < ROWS; i++) {
int *row = maze[i];
for(int j = 0; j < LEN; j++) {
printf("%d\t", row[j]);
}
printf("\n");
}
}
現在已經認識指標了,上例中的 maze[i]
取得其實是每列一維陣列的首元素位址,然而指定給 int*
的 row
的話,如稍早談到的,row
就只會儲存位址,也就是 row
並沒有每列一維陣列的長度資訊。
雖說如此,對多數情境來說,這種從二維陣列中取得每列的方式已經足夠,類似地,若不管長度資訊會失去的問題,也可以如下模擬二維陣列:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int row1[LEN] = {1, 2, 3};
int row2[LEN] = {4, 5, 6};
int* maze[ROWS] = {row1, row2};
for(int i = 0; i < ROWS; i++) {
int *row = maze[i];
for(int j = 0; j < LEN; j++) {
printf("%d\t", row[j]);
}
printf("\n");
}
return 0;
}
說是模擬的原因在於,maze
實際上是 int*
的一維陣列,maze[0]
、maze[1]
僅儲存 row1
、row2
首元素的位址,並沒有 row1
、row2
的長度資訊,雖說如此,對大多數情境來說,想用一維陣列組合出二維陣列,以上的方式也已經足夠。
接下來純粹是挑戰,可以自行研究一下,就不多做說明了。以下程式示範了如何取得二維陣列中的每一列,並保留長度資訊:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
for(int i = 0; i < ROWS; i++) {
int (*row)[LEN] = &maze[i];
for(int j = 0; j < LEN; j++) {
printf("%d\t", (*row)[j]);
}
printf("\n");
}
}
有沒有辦法完全基於指標來迭代陣列,而不是依靠索引呢?
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
int(*mazeBegin)[LEN] = maze;
int(*mazeEnd)[LEN] = *(&maze + 1);
for(int(*row)[LEN] = mazeBegin; row < mazeEnd; row++) {
int *begin = *row;
int *end = *(row + 1);
for(int* it = begin; it < end; it++) {
printf("%d\t", *it);
}
printf("\n");
}
return 0;
}
底下的寫法,maze[0]
、maze[1]
會失去 row1
、row2
的長度資訊:
int row1[LEN] = {1, 2, 3};
int row2[LEN] = {4, 5, 6};
int* maze[ROWS] = {row1, row2};
有沒有辦法不失去長度資訊呢?
#include <stdio.h>
#define ROWS 2
#define LEN 3
typedef int(*Row)[LEN];
int main(void) {
int row1[LEN] = {1, 2, 3};
int row2[LEN] = {4, 5, 6};
Row maze[ROWS] = {&row1, &row2};
int rows = *(&maze + 1) - maze;
for(int i = 0; i < rows; i++) {
Row row = maze[i];
int len = *(row + 1) - *row;
for(int j = 0; j < len; j++) {
printf("%d\t", *(*row + j));
}
printf("\n");
}
return 0;
}
typedef
可用來為指定的型態取別名,就上例來說,為 int(*)[LEN]
取了個別名 ROW
,這樣比較便於使用 ROW
來宣告,當然,這些挑戰的寫法不容易理解,純粹就是探討,程式基本上還是選擇各自情境下易懂的寫法會比較好。