天天看點

18. C語言 -- 指針數組和數組指針1. 指針和數組的差別2. 指針數組3. 指針的步長4. 數組指針參考

本部落客要内容為 “小甲魚” 視訊課程《帶你學C帶你飛》【第一季】 學習筆記,文章的主題内容均來自該課程,在這裡僅作學習交流。在文章中可能出現一些錯誤或者不準确的地方,如發現請積極指出,十分感謝。

也歡迎大家一起讨論交流,如果你覺得這篇文章對你有所幫助,記得評論、點贊哦 ~(。・∀・)ノ゙

1. 指針和數組的差別

  指針是左值,而數組名隻是一個位址常量,它不可以被修改,是以數組名不是左值。其中的左值在 《12. C語言 – 拾遺》 中的 1.1 部分有講,lvalue 指用于識别或定位一個存儲位置的辨別符,同時還必須是可改變的。

  比如說在下面的這段程式中

#include <stdio.h>

int main()
{
	char str[] = "I love LunZiGongChang!";
	int count = 0;

	while (*str++ != '\0'){
		count++;
	}

	printf("總共有%d個字元!\n", count);

	return 0;
}
           

執行代碼會得到如下的錯誤

18. C語言 -- 指針數組和數組指針1. 指針和數組的差別2. 指針數組3. 指針的步長4. 數組指針參考

通過錯誤提醒可以知道,自加運算符 ++ 需要一個左值,雖然數組名是數組第一個元素的位址,但是他是不可變的,不滿足左值的要求,即不是一個左值。但是指針是一個左值,是以我們隻需要初始化一個指針為數組第一個元素的位址,就可以解決這個問題,具體代碼如下

#include <stdio.h>

int main()
{
	char str[] = "I love LunZiGongChang!";
	char *p= str;
	int count = 0;

	while (*p++ != '\0'){
		count++;
	}

	printf("總共有%d個字元!\n", count);

	return 0;
}
           

其中還有一點需要注意的就是,自加運算符 ++ 與取值運算符 * 相比,++ 的優先級要更高,是以

*p++

相當于先将指針指向目前位置的下一個位置,然後再取出目前位址的值,實際上就是在逐個的取出字元串中的值。執行上面的代碼可以得到如下的結果

總共有22個字元!
           

這個地方還可以引申出一個問題:c 中while(*p++);與while(*p){p++;}有什麼差別?

差別在于退出循環後, p的值不一樣。

while( *p++ ); //當*p=0時,退出循環,此時p++仍然執行了
while( *p ) p++; //當*p=0時,退出循環,此時p++不再被執行
           

例如

char *p="ABCD";

執行完第一個while循環後,p指向的是’\0’後面的一個位元組,p的結果是未知的

而如果是執行第二個循環,則p指向的是’\0’,也就是’D’後面的一位元組,即p=’\0’。如果忘記了可以回顧一下之前的文章《12. C語言 – 拾遺》中第三部分 “自增自減運算符” 中的内容。

  上面這段代碼是不是和 《17. C語言 – 指針和數組的關系》 第三部分 指針的運算 很相似。在指針的運算中,我們是使用指針的方式定義了一個數組,因為指針中存放的是數組中第一個元素的位址,而數組中第一個元素的位址又是數組名,是以對于指針定義的數組,既可以使用數組的形式通路,又可以使用指針運算的方式通路。但是對于直接用數組形式定義的數組,由于數組名雖然和數組中第一個元素的位址相等,但是并不是一個左值,是以隻可以數組的形式通路數組中元素,不可以使用指針的形式通路,除非向上面那樣新定義一個指針。

2. 指針數組

  指針數組,從名字來了解,很容易看出它是一個數組,裡面裝的是指針。比如下面的這段代碼

它就是一個指針數組,我們可以從運算符的優先級和結合性進行分析。數組下标的優先級要比取值運算符的優先級高,是以先入為主,p1 被定義為具有 5 個元素的數組。那麼數組元素的類型呢?是整型嗎?顯然不是,因為還有一個星号,是以它們應該是指向整型變量的指針。所示上述代碼所定義的數組如下所示

18. C語言 -- 指針數組和數組指針1. 指針和數組的差別2. 指針數組3. 指針的步長4. 數組指針參考

即指針數組是一個數組,數組中的元素是指針變量。

  比如說下面這段代碼

#include <stdio.h>

int main()
{
        char *p1[5] = {
                "輪子工廠廠長招親!",
                "身高不限",
                "膚色不限",
                "身材不限",
                "隻要你不嫌棄廠長醜帥醜帥的~"
        };
        int i;

        for (i = 0; i < 5; i++){
                printf("%s\n", p1[i]);
        }

        return 0;
}
           

我們将指針數組中的每個元素初始化為一個字元串,這裡之是以可以這樣寫是因為一個指針可以使用

char *p = "sss"

的方式進行初始化,是以如果想初始化一個指針數組,就可以通過上面的方式進行。在列印輸出中使用

p1[i]

而不是

*p1[i]

*p1[i]

将取出的是字元串中的第一個字元,而不能列印整個字元串。執行上面的代碼會得到如下的結果

輪子工廠廠長招親!
身高不限
膚色不限
身材不限
隻要你不嫌棄廠長醜帥醜帥的~
           

3. 指針的步長

  要了解數組指針,首先要加深對指針的了解。指針的類型決定了指針的視野,指針的視野決定了指針的步長。我們必須清楚一個指針變量将告訴我們兩個資訊:某個資料結構的起始位址,以及該結構的跨度。比如 int p = &a; 說明該指針指向變量 a 的起始位址,以及它的跨度為 sizeof(int)。是以 p + 1 == p + sizeof(int)。

  是以不同的數組有着不同的指針步長。在這裡我們首先從數組名的角度進行考慮,對于一個一維數組

int array[3]={1,2,3};

,數組名是數組的首位址,是一個 int 型的指針,這點很明顯。對于一個二維數組

int array[2][3]={{1,2,3},{4,5,6}};

來講,由于記憶體中的二維數組是以一維數組的形式存放的,是以二維數組是嵌套定義的 ,這個二維數組就是由兩個一維數組array[0]和array[1]組成的。其中

  • array[0]:是第一個一維數組的數組名,這個一維數組是{1,2,3};
  • array[1]:是第二個一維數組的數組名,這個一維數組是{4,5,6};

這個時候數組從 array[0] 到 array[1] 雖然隻變換了一個位置,但實際上跳過了整個第一行,是以

  • array數組的元素類型為: int (*)[3]類型 //步長為 3 的指針
  • array[0]數組的元素類型為: int *類型
  • array[1]數組的元素類型為: int *類型

  同理對于一個三維數組

int Sarray[2][2][3] = {
{ { 1, 2, 3 }, { 4, 5, 6 } },
{ { 7, 8, 9 }, { 3, 6, 8 } }
};
           
  • Sarray: 是指向 int (*)[2][3]類型的指針;//步長為 2行3列的指針。
  • Sarray[0]:是指向int *[3]類型的指針;
  • Sarray[0][0]:是指向int *類型的指針;

是以可以看到,對于數組名來講,他的确是數組第一個元素的位址,但是他的步長是根據他的第一個元素的數量确定的,無論是多少維的數組,把它看成一個一維數組,裡面包含着怎樣的子數組,就有怎樣的步長。

4. 數組指針

  數組指針,顧名思義,是一個指向數組的指針,比如說下面這個

從運算符的優先級和結合性進行分析,因為圓括号和數組下标位于同一個優先級隊列,是以我們就要看先來後到的問題了。由于它們的結合性都是從左到右,是以 p2 先被定義為一個指針變量。那麼它指向誰?還能有誰?後邊還緊跟着一個具有 5 個元素的數組,p2 指向的就是它。由于指針變量的類型事實上就是它所指向的元素的類型,是以這個 int 就是定義數組元素的類型為整型。即如下圖所示

18. C語言 -- 指針數組和數組指針1. 指針和數組的差別2. 指針數組3. 指針的步長4. 數組指針參考

  是以數組指針是一個指針,它指向的是一個數組。那麼數組名,數組第一個元素的位址,數組指針這三者之間是什麼關系呢?我們看下面的這段程式,它将列印出這3個指針的值,并且通過指針的方式列印出指針所指向的下一個元素的位址。

#include <stdio.h>

int main()
{
	int temp[5] = {1, 2, 3, 4, 5};
	int (*p)[5] = &temp;
	int *pp = temp;

	printf("%p\n", temp);
	printf("%p\n", &temp[0]);
	printf("%p\n", &temp);
	printf("----------------\n");
	printf("%p\n", pp);
	printf("%p\n", pp+1);
	printf("%p\n", p);
	printf("%p\n", p+1);
	printf("%p\n", *p+1);

	return 0;
}
           

輸出的結果如下

0x7ffe2758e710
0x7ffe2758e710
0x7ffe2758e710
----------------
0x7ffe2758e710
0x7ffe2758e714
0x7ffe2758e710
0x7ffe2758e724
0x7ffe2758e714
           

  可以看到數組名,數組第一個元素的位址和數組指針這三個指針指向的都是同一個位址,那麼是否意味着三這是完全等價的呢?在上面的代碼中可以看到 pp 指針指向數組的首位址,他的下一個指向的數組中第二個元素的位址;而作為數組指針的 p 指針雖然也指向數組的首位址,但是他的下一個,卻指向數組外面的位置(與數組首位址相差了 20,16 進制的 14 是 20),隻用通過 *p+1 才會得到數組中第二個原始的位址,這就涉及到指針步長這個概念了。

  就像剛剛所講的,實際上這裡

&temp

對數組取址就是将整個數組看作是一個元素,那麼指針 int (*p)[5] = &temp; 的跨度就很明顯是 5 啦~這也就解釋了為什麼指向數組首位址的指針的下一個是數組中的第二個元素的位址,而數組指針的下一個會指向數組的最後(實際上是數組最後一個元素的後一個位置的位址)。

  根據上面的知識可以知道,下面的代碼明顯是錯誤的。

#include <stdio.h>

int main()
{

	int (*p2)[5] = {1, 2, 3, 4, 5};
	int i;

	for (i = 0; i < 5; i++)
	{
		printf("%d\n", *(p2 + i));
	}

	return 0;
}
           

它的本來用意是想用指針法的形式将數組中的每一個元素列印出來,但是卻得到如下的結果

18. C語言 -- 指針數組和數組指針1. 指針和數組的差別2. 指針數組3. 指針的步長4. 數組指針參考

從 warning 的提示資訊可以看出是第六行指針的定義及初始化的錯誤。

  在上面的程式中,數組指針是指向數組的指針,但是

int (*p2)[5] = {1, 2, 3, 4, 5};

表示指針變量 p2 是指向數組中的第一個元素的位址,并不是指向數組的位址。數組指針和數組的頭位址雖然在數值上相等,但是兩者的步長卻是不等的,這上面 warningd 的原因,也是結果錯誤的原因。

  是以我們對代碼進行了如下的修改,将數組指針初始化為整個數組的位址(即代碼中的 5 6 行)。

#include <stdio.h>

int main()
{
	int temp[5] = {1, 2, 3, 4, 5};
	int (*p)[5] = &temp;
	int i;

	for (i = 0; i < 5; i++){
		printf("%d\n", *(p + i));
	}

	return 0;
}
           

執行之後發現程式依然會報錯,如下所示

18. C語言 -- 指針數組和數組指針1. 指針和數組的差別2. 指針數組3. 指針的步長4. 數組指針參考

很明顯又錯了,從 warning 的資訊來看 printf 使用一個 %d 作為占位符,但是傳入的确實一個整型指針。這個很好了解,因為 p 指針還是一個指向數組首位址,步長為5的指針。

  将程式修改為如下的形式便可以正常執行了

#include <stdio.h>

int main()
{
	int temp[5] = {1, 2, 3, 4, 5};
	int (*p)[5] = &temp;
	int i;

	for (i = 0; i < 5; i++){
		printf("%d\n", *(*p + i));
	}

	return 0;
}
           

  結果就是輸出 1 2 3 4 5 ,這裡就不截圖了。需要強調的是在列印輸出的過程中使用了

printf("%d\n", *(*p2 + i));

,在 p2 的前面增加了一個 * ,有兩種方式了解這種改動。一種是 temp 前面多了一個取址運算符,p2 前面就要對應着增加一個取值運算符;另一種了解方式是,temp 是數組名,實際上就是數組中第一個元素的位址,對位址再進行取址,是以 p2 代表的是數組自一個元素位址的位址,這個時候 *p2 代表數組第一個元素的位址,*p2 + i 就是後面第 i 個元素的位址,然後再取值就可以獲得數組中的元素了。

  我們可以從下面這個例子加深自己對數組指針的了解

#include <stdio.h>

int main()
{
        int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int *p = (int *)(&array + 1);

        printf("%d\n", *(p - 6));

        return 0;
}
           

如果将數組指針與數組第一個元素的位址等價起來的話,上面的代碼很明顯是越界了的,但實際上編譯執行之後輸出了結果

4

。實際上是因為雖然 array 和 &array 的值相同,但步長是不一樣的。

  是以,&array + 1 指向的就是整個數組最後的位置(第二個 array 數組的起始位置),然後 (int *) 将其強制轉換為一個整型位址(指針),是以指針變量 p 初始化後,指向的位址應該是 array[10](第 11 個元素),是以 *(p - 6) == array[10 - 6] == array[4] == 4。

  重點強調:array 是數組第一個元素的位址,是以 array + 1 指向數組第二個元素;&array 是整個數組的位址,是以 &array + 1 指向整個數組最後的位置,也就是兩者指針的步長是不一樣的。

  上面的例子是從數組指針的角度進行考慮的,下面的這個例子将從指針數組的角度進行考慮,比如下面的這段代碼會輸出和上面一樣的結果。

#include <stdio.h>

int main()
{
        int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int (*p)[10] = &array;

        printf("%d\n", *(*(p+1)-6));

        return 0;
}
           

參考

[1] “小甲魚” 視訊課程《帶你學C帶你飛》【第一季】P23

[2] 涼涼貓 CSDN部落格 《二維數組及多元數組的指針總結》

[3] 百度知道 《c++中while(*p++);與while(*p){p++;}有什麼差別?》

歡迎大家關注我的知乎号(左側)和經常投稿的微信公衆号(右側)

18. C語言 -- 指針數組和數組指針1. 指針和數組的差別2. 指針數組3. 指針的步長4. 數組指針參考

繼續閱讀