1. 程式人生 > 其它 >指標與陣列

指標與陣列

1、定義

指標:C語言中某種資料型別的資料儲存的記憶體地址,例如:指向各種整型的指標或者指向某個結構體的指標。

陣列:若干個相同C語言資料型別的元素在連續記憶體中儲存的一種形態。

陣列在編譯時就已經被確定下來,而指標直到執行時才能被真正的確定到底指向何方。所以陣列的這些身份(記憶體)一旦確定下來就不能輕易的改變了,它們(記憶體)會伴隨陣列一生;

而指標則有很多的選擇,在其一生他可以選擇不同的生活方式,比如一個字元指標可以指向單個字元同時也可代表多個字元等。

指標和陣列在C語言中使用頻率是很高的,在極個別情況下,陣列和指標是“通用的”,比如陣列名錶示這個陣列第一個資料的指標。

如下程式碼

#include <stdio.h>
char
array[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語言進階專輯