關於C語言中指針和數組的爭論就像是一場惡戰。一方面,有些人覺得,所有人都必須承認指針與數組是不同的。而另一些人則認爲數組被當成指針來處理,因此它們不應該有什麼區別。這種現象讓人迷惑。然而,這兩種說法其實都是正確的。下面是相關的知識,歡迎閱讀。
數組不是指針,指針也不能說是數組。在C語言中,指針僅在內存中代表一個地址,而數組是許多連續的內存塊,多個類型相似的元素存儲在其中。更深入的解釋,請參考我之前的博文《C語言內存地址》。在C語言的絕大多數情況下,數組被當作指針來處理,這也是使人困惑的.地方。
數組表示法vs指針表示法
數組被當作指針來處理,具體指的下面兩條:
數組名變量代表了數組中第一個元素的地址。它並不是一個指針,但卻表現得像一個不能被修改的常指針一樣。
程序在與數組交互的時候,用指針表示法代替數組表示法。
我們來看點代碼吧。
// 初始化數組
int numbers[5] = {1,2,3,4,5};
// 標準數組表示法
int *ptr1 = numbers;
int val1 = numbers[0];
// 數組表示法取地址
int *ptr2 = &numbers[0];
int val2 = *(&numbers[0]);
// 指針加偏移表示法
int *ptr3 = numbers + 0;
int val3 = *(numbers + 0);
// 輸出指針中的地址
printf("*ptr1 = %p", (void *)ptr1);
printf("*ptr2 = %p", (void *)ptr2);
printf("*ptr3 = %p", (void *)ptr3);
// 輸出地址指向的int值
printf("val1 = %d", val1);
printf("val2 = %d", val1);
printf("val3 = %d", val1);
我們聲明瞭一個包含5個int的數組,並將數組名變量numbers賦給了一個int指針,ptr1。numbers代表了這個數組第一個元素的地址,將其賦給ptr1正是把它當成了指針來使用。接着我們用數組表示法訪問了第一個元素的值。
第二個例子中,我們用數組表示法取了數組中第一個元素的地址,之後我們用解引用第一個元素所在地址的方法訪問了它。
第三個例子中,我們用指針運算將數組中第一個元素的地址賦值給ptr3,之後我們解引用相同的地址來得到它的值。
最後我們將所有存儲在指針中的地址和所有在這些地址的int值輸出到屏幕上。運行這段代碼,你會得到類似下面的輸出:
*ptr1 = 0x7fff6be1de60
*ptr2 = 0x7fff6be1de60
*ptr3 = 0x7fff6be1de60
val1 = 1
val2 = 1
val3 = 1
所有值都是相同的。接下來再看看下面的代碼:
// 初始化數組
int numbers[5] = {1,2,3,4,5};
int i = 0;
// 用數組表示法輸出元素
for (i = 0; i < 5; i++ ) {
int value = numbers[i];
printf("numbers[%d] = %d", i, value);
}
// 用指針加偏移輸出元素(真討厭)
for (i = 0; i < 5; i++ ) {
int value = *(numbers + i);
printf("*(numbers + %d) = %d", i, value);
}
// 僅用一個指針輸出元素
int *ptr = numbers;
for (i = 0; i < 5; i++ ) {
int value = *ptr++;
printf("%d, *ptr++ = %d", i, value);
}
運行它,你會得到以下輸出:
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
numbers[3] = 4
numbers[4] = 5
*(numbers + 0) = 1
*(numbers + 1) = 2
*(numbers + 2) = 3
*(numbers + 3) = 4
*(numbers + 4) = 5
0, *ptr++ = 1
1, *ptr++ = 2
2, *ptr++ = 3
3, *ptr++ = 4
4, *ptr++ = 5
就像你看到的那樣,所有過程得到了相同的結果。
數組表示法實際上就是指針運算。C語言標準只是將numbers[0]定義爲*(numbers + 0)的語法糖。(譯者注:語法糖,它意指那些沒有給計算機語言添加新功能,而只是對人類來說更容易理解的語法。)無論何時,你寫下一個數組表示法,比方說numbers[2],都會被編譯器轉換爲*(numbers + 2)。這裏,numbers表示數組中第一個元素的地址,+2則表示用於指針運算的偏移量。
數組變量
我們已經展示了,數組常被當作指針來處理,而且對於C編譯器而言數組表示法就是指針運算。一些人自然而然地就做出了這樣的假設:既然數組能被當成指針,指針也應該能賦值給數組。這是不對的,數組名變量不能被改變。我們看看下面的代碼吧。
// 初始化數組
int numbers[5] = {1,2,3,4,5};
int numbers2[5] = {6,7,8,9,0};
int *ptr = numbers2;
// 這不能通過編譯
numbers = numbers2;
numbers = &numbers2;
numbers = ptr;
這段代碼不能通過編譯。試一試,你會得到以下輸出。
incompatible types when assigning to type ‘int[5]’ from type ‘int *’
incompatible types when assigning to type ‘int[5]’ from type ‘int (*)[5]’
incompatible types when assigning to type ‘int[5]’ from type ‘int *’
雖然數組名變量代表了數組第一個元素的地址,它卻表現得像一個不能被更改的常指針一樣。它不能接受一個別的數組名變量或是指向另一個數組的指針的賦值。思考一下,如果你有一個數組名變量A代表一個數組,而且你能夠改變A的地址,那被A指向的內存將會發生生麼?
接下來看看這段能夠編譯的代碼。
// 初始化數組
int numbers[5] = {1,2,3,4,5};
int numbers2[5] = {6,7,8,9,0};
int *ptr1 = numbers;
int *ptr2 = numbers2;
// 能通過編譯
ptr1 = ptr2;
// 輸出地址
printf("numbers = %p", (void *)numbers);
printf("numbers2 = %p", (void *)numbers2);
printf("ptr1 = %p", (void *)ptr1);
printf("ptr2 = %p", (void *)ptr2);
它會輸出這樣的結果:
numbers = 0x7fff5ea3d230
numbers2 = 0x7fff5ea3d250
ptr1 = 0x7fff5ea3d250
ptr2 = 0x7fff5ea3d250
雖然不能直接改變數組名變量,我們仍然改變一個指向這個數組的指針。代碼中,我們創建了兩個數組,兩個int指針。我們將numbers賦給了ptr1,將numbers2賦給了ptr2。接着我們將ptr2賦給了ptr1,最後輸出結果。可以看到,ptr1和ptr2都指向了numbers2數組的第一個元素。
總結
我希望你們能夠喜歡這篇對C語言中數組和指針的概述。我們沒有囊括關於指針和數組的一切知識,但足以作爲一個開始。跟往常一樣,我非常願意接受大家的評論和建議。