指標與陣列
1、定義
指標:C語言中某種資料型別的資料儲存的記憶體地址,例如:指向各種整型的指標或者指向某個結構體的指標。
陣列:若干個相同C語言資料型別的元素在連續記憶體中儲存的一種形態。
陣列在編譯時就已經被確定下來,而指標直到執行時才能被真正的確定到底指向何方。所以陣列的這些身份(記憶體)一旦確定下來就不能輕易的改變了,它們(記憶體)會伴隨陣列一生;
而指標則有很多的選擇,在其一生他可以選擇不同的生活方式,比如一個字元指標可以指向單個字元同時也可代表多個字元等。
指標和陣列在C語言中使用頻率是很高的,在極個別情況下,陣列和指標是“通用的”,比如陣列名錶示這個陣列第一個資料的指標。
如下程式碼
#include <stdio.h> chararray[4] = {1, 2, 3, 4}; int main(void) { char * p; int i = 0; p = array; for (; i < 4; i++) { printf("*array = %d\n", *p++); } return (0); }
這裡我們將陣列名array作為陣列第一個資料的指標賦值給p。但是不能寫成*array++。準確來說陣列名可以作為右值,不能作為左值(左值和右值的概念這裡不再展開講解)。
陣列名的值其實是一個指標常量,這樣我想你就明白了陣列名為什麼不能做為左值了。如果想用指標p訪問array的下面2的資料,以下寫法是合法的
char data; /*第一種寫法*/ p = array; data = p[2]; /*第二種寫法*/ p = array; data = *(p+2); /*第三種寫法*/ p = array + 2; data = *p;
2、指標與二維陣列
先說一下二維陣列,二維陣列在概念上是二維的,有行和列,但在記憶體中所有的陣列元素都是連續排列的,它們之間沒有“縫隙”。以下面的二維陣列 a 為例:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
從概念上理解,a 的分佈像一個矩陣:
0 1 2 3
4 5 6 7
8 9 10 11
但是記憶體是連續的,沒有這樣的“矩陣記憶體”,所以二維陣列a分佈是連續的一塊記憶體。
C語言允許把一個二維陣列分解成多個一維陣列來處理。對於陣列 a,它可以分解成三個一維陣列,即 a[0]、a[1]、a[2]。每一個一維陣列又包含了 4 個元素,例如 a[0] 包含 a[0][0]、a[0][1]、a[0][2]、a[0][3]。那麼定義如下指標如何理解呢?
int (*p)[4];
括號中的*表明 p 是一個指標,它指向一個數組,陣列的型別為int [4],這正是 a 所包含的每個一維陣列的型別。
那麼和下面定義有什麼區別呢?
int *p[4];
這裡就要先說明*和[]的優先順序了,[]的優先順序是高於*的,所以int *p[4];等同於int *(p[4]);。所以它是一個指標陣列。
這裡很繞,總接下
int (*p)[4];是陣列指標,它指向二維陣列中每個一維陣列的型別,它指向的是一個數組。
int *p[4];是指標陣列,它是一個數組,陣列中每個數是指向int型的指標。
3、指標陣列與陣列指標
對於指標陣列,說的已經很明確了,不再詳細講解,下面說一下陣列指標。舉例看一下
#include <stdio.h> int main() { int a[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}; int(*p)[4]; p = a; printf("%d\n", sizeof(*(p + 1))); return (0); }
對於陣列指標p如下
那麼printf("%d\n", sizeof(*(p + 1)));的結果就是16。
如果想列印a[1][0]的值,程式碼如下
printf("%d\n", *(*(p + 1)));
如果想列印a[1][1]的值,程式碼如下
printf("%d\n", *(*(p + 1)+1));
這個自行體會,p是陣列指標,它指向的是一個數組,所以對獲取它指向的值,也就是*p,是指向一個數組還是一個值,指向a[0]。獲取獲取a[0][0],就需要寫成**p。
對指標進行加法(減法)運算時,它前進(後退)的步長與它指向的資料型別有關,p 指向的資料型別是int [4],那麼p+1就前進 4×4 = 16 個位元組,p-1就後退 16 個位元組,這正好是陣列 a 所包含的每個一維陣列的長度。也就是說,p+1會使得指標指向二維陣列的下一行,p-1會使得指標指向陣列的上一行。
最後再次捋一下陣列指標和指標陣列,
int *p1[4];是指標陣列。
int (*p2)[4];是陣列指標。
“[]”的優先順序比“*”要高。
對於指標陣列,p1先和“[]”結合,構成一個數組的定義,陣列名為p1,int *修飾的是陣列的內容,即陣列的每個元素。那麼它本質是一個數組,這個數組裡有4個指向int型別資料的指標。
對於陣列指標,“()”的優先順序比“[]”高,“*”號和p2 構成一個指標的定義,指標變數名為p2,int 修飾的是陣列的內容,即陣列的每個元素。陣列在這裡並沒有名字,是個匿名陣列。那麼它本質是一個指標,它指向一個包含4個int 型別資料的陣列。
既然深入談了指標陣列和陣列指標,就多聊一下。
#include <stdio.h> int main() { char a[5] = {'A', 'B', 'C', 'D'}; char(*p3)[5] = &a; char(*p4)[5] = a; return 0; }
上面程式碼是編譯編譯是報了waring的,報警如下
注意:不同的編譯器編譯結果可能不同,我的編譯方法請參考《使用vscode編譯C語言》。
p3 這個定義的“=”號兩邊的資料型別完全一致,而p4 這個定義的“=”號兩邊的資料型別就不一致了。左邊的型別是指向整個陣列的指標,右邊的資料型別是指向單個字元的指標。所以才有了上面的警告。
但由於&a 和a 的值一樣,而變數作為右值時編譯器只是取變數的值,所以執行並沒有什麼問題。不過編譯器仍然警告你別這麼用。
再舉一個栗子
int vector[10]; int matrix[3][10]; int *vp,*vm; vp = vector; vm = matrix;
上面的程式碼第5行是錯誤的,因為vm是指向整型的指標,但是matrix不是指向正向的指標,他是指向整型陣列的指標。下面是正確的寫法
int matrix[3][10]; int (*vm)[10]; vm = matrix;
4、陣列指標的應用
上面說了那麼多,可能大部分開發者用不到,陣列指標在很多時候都是可以代替二維陣列的,有些程式設計師喜歡用指標陣列來代替多維陣列,一個常見的用法就是處理字串。
#include <stdio.h> char *Names[] = { "Bill", "Sam", "Jim", "Paul", "Charles", 0}; void main() { char **nm = Names; while (*nm != 0) printf("%s \n", *nm++); }
具體執行我就不講解了,執行結果如下
注意陣列中的最後一個元素被初始化為0,while迴圈以次來判斷是否到了陣列末尾。具有零值的指標常常被用做迴圈陣列的終止符。
這種零值的指標稱為為空指標(NULL)。採用空指標作為終止符,在增刪元素時就不必改動遍歷陣列的程式碼,因為此時陣列仍然以空指標作為結束。
5、騷操作
寫到這裡想到一個“騷操作”,先看下面程式碼是否正確。
p[-1]=0;
初看這句程式碼,覺得奇怪,甚至覺得它就是錯誤,日常C語言開發基本有見到小標是負數的,但是仔細想想沒有哪一本書說過下標能為負數的。看下面程式碼
void main() { int data[4] = {0, 1, 2, 3}; int *p; p = data +2; printf("p[-1] is %d\n",p[-1]); printf("*(p-1) is %d\n",*(p-1)); }
執行結果如下
不僅可以編譯通過,還能正確的輸出結果為1。這表明,C的下標引用和間接訪問表示式是一樣的。當然不鼓勵這種騷操作,程式碼需要很強的可讀性,而不是這樣的騷操作,這裡只是演示下標引用和簡介表示式的關係。
點選檢視本文所在的專輯:C語言進階專輯