昨天剛把《C程式設計語言》中“指標與陣列”章節讀完,終於把心中的疑惑徹底解開了。現在記錄下我對指標宣告的理解,順便說下如何在C語言中建立複雜宣告以及讀懂複雜宣告

本文章中的內容參考自《C程式設計語言》

指標是什麼就不詳細說明了,用一句話來總結就是:“指標是一種儲存變數地址的變數”。

1.宣告簡單的指標變數

先看看程式碼:

int i = 1;
int *p;     //宣告一個指向int型別資料的指標變數 p
p = &i;     //&為取地址符,把變數i的地址賦值給指標 p
*p = 2;     //此時 i 的值變成2了

這段程式碼聲明瞭一個指標變數p,並把它指向變數i,通過*p可以訪問到變數i,並對i的值進行修改。現在對星號 * 的作用進行詳細的講解。* 是一元運算子,它用在不同的地方將具有不同的作用。

1.1 星號 * 用於宣告語句時的作用

上面的程式碼片段中的第2行程式碼“int *p;”就是星號 * 用於宣告語句時的情況。對於指標的宣告,我們首先要從 p 這裡開始看起,這是C語言中“宣告”的語法,下面會介紹到。
第一步:先看p的右邊有沒有其它符號(分號不算),可以看到p的右邊並沒有符號。

第二步:看p的左邊,在p的左邊有一個星號 * ,關鍵的時候到了!在這裡可以看到星號 * 作用於p上面,其產生的效果是:“宣告變數 p 是一個指標 ”。目前為止,我們已經知道 p 是一個指標了,但還不知道該指標是指向什麼型別資料的。

第三步:看星號 * 右邊的型別說明符 int,在這裡int的作用就是宣告 p 指向的資料型別是int。至此,“int *p;”這句程式碼就完全解釋完了。

注意!上面的 int 、* 這兩個符號發揮作用是有先後的,並不是組合在一起來發揮作用的。先是 * 發揮作用:宣告p是一個指標變數。然後是int發揮作用:宣告p指向的資料型別是整型資料

1.2 星號 * 用於宣告語句之外時作用

以上程式碼片段中,最後一句“ *p = 2; ”就是星號 * 用於宣告語句之外時的作用。這句語句等效於“ i = 2;”。
用專業點的說法就是:如果指標p指向整形變數 i,那麼在 i 出現的任何上下文中都可以使用 *p ,當 * 作用於指標變數p時,就是訪問指標變數p所指向的變數 i。

至此,已經說了如何宣告簡單的指標變數,是不是覺得很簡單呢?接下來將會按照複雜程度遞增的順序來介紹和指標有關的複雜宣告

2.複雜宣告和宣告語法

2.1宣告指向指標的指標變數

先看程式碼:

int i = 1;
int *p = &i;    //宣告指標變數p,p指向變數i
int **pp;       //宣告指標變數pp
pp = &p;        //pp指向變數p,p是指標變數
**pp = 2;       //此時 i 的值變成 2 了
//*(*pp) = 2;這個語句等效於上面的語句

程式碼段中的1、2行在上面已經介紹過了,現在主要介紹第2行之後的程式碼。
注意,這下面這兩句是等效的,這是因為類似於 * 和++這樣的一元運算子遵循從右至左的結合順序。

int **pp;   //宣告方式1
int *(*pp); //宣告方式2

現在開始解釋宣告方式2(和宣告方式1是一樣),同上面的簡單宣告一樣。我們再次從變數名pp開始。
第一步:先看看pp右邊有沒有符號,可以看到pp的右邊有一個右括號,而括號只是強調結合順序,所以不用管它。

第二步:看pp的左邊,可以看到,pp的左邊有一個星號 * (從右數起第一個星號),該星號的作用是:“宣告變數pp是一個指標。此時,我們還不知道指標pp所指向的資料型別,並且把括號裡面的 * 和 pp 兩個符號都解釋完了。現在看看括號外面的符號。

第三步:先看括號外面的右邊,可以看到,括號外面的右邊並沒有符號。

第四步:看括號左邊,這時,我們看到括號外面的左邊有一個星號 * (從右數起,第二個星號 *),這個星號 * 的作用是:“宣告指標變數pp所指向的資料型別是指標型別”。此時,我們已經知道了pp指向的資料型別是指標型別(即是程式碼片段中 p 的型別),但還不知道所指向的指標是指向什麼型別資料的。例如,pp指向指標p,但卻不知道p指向的是什麼型別的資料。

第五步:看最左邊的符號“int”,這個int的作用就是:宣告pp所指向的指標所指向的資料型別是整型int。例如,pp指向指標p,現在我們知道了p指向的資料型別是int整型。

哈哈,是不是有點暈了,現在簡單總結下:pp右邊第一個星號 * 聲明瞭pp是一個指標變數,第二個星號 * 聲明瞭指標變數pp所指向的資料型別是指標型別,而型別說明符int則聲明瞭pp所指向的指標指向的資料型別是int

看到這裡,你可能會疑惑:一定要按照上面的步驟來解釋宣告嗎?這是套路嗎?

答:是的。因為這是C語言中”宣告“的語法,下一小節將會對該語法進行介紹。之所以這麼遲才介紹這個語法,是因為在用過後,會更容易理解它。

2.2 C語言中”宣告“的語法

在這裡,將會對宣告的語法進行講解,為後面理解更加複雜的宣告打基礎。

在C語言中解釋一個宣告,要先從被宣告的變數名開始解釋。並不是從左到右,也不是從右到左解釋,而是從中間開始,更準確來講,是從被宣告的變數名開始解釋。而宣告總是由很多符號和唯一的變數名相結合,這些符號和唯一的變數名結合就是宣告符。宣告的形式為
”T D“的形式,其中 T代表型別,D代表宣告符。舉個例子吧:

int *p;

int就是T,而*p就是D。

下面將以變數名p來對宣告中用到的符號進行解釋。

變數名p可以和很多符號來結合成宣告符,如[ ],(),*。

1.當p與符號 [ ] 結合時(結合在p的右邊),符號 [ ]的作用就是宣告變數p是一個數組型別[ ]裡面的數字則決定了陣列中元素的個數。如下面的宣告程式碼:

int p[5];//宣告變數p是一個整型陣列,陣列中有5個元素

需要注意的是,符號[ ]的優先順序是比星號 * 的優先順序高的。

2.當p與符號 ( )結合時(結合在p的右邊),符號( )的作用就是:宣告p是一個函式,通過p()可以呼叫該函式,()中可以有引數列表或者無引數列表,如下面程式碼:

int p();//宣告一個函式p,返回值由前面的int決定
int p(int a,int b); //宣告一個帶引數的函式p

同樣,符號( )的優先順序比星號 * 高。

3.當p與星號 * 相結合時(結合在左邊),符號 * 的作用就是宣告p是一個指標型別 。如下程式碼 :

int *p; //宣告p是一個指標,該指標指向int型別資料

介紹完這三個符號後,可以繼續介紹語法(套路)了。

在解釋宣告時,首先要決定宣告的名字p是什麼東西,而和p最近的符號則決定了p是什麼東西。如下面宣告:

int p();    //p是函式
int *p;     //p是指標
int p[5];   //p是陣列
int *p[5];  //p是陣列,裡面的5個元素為指向int型別的指標

這裡的第4行程式碼要解釋一下,由於[ ]的優先順序比星號 * 高,所以[ ]先作用於p,故p是一個數組,現在我們知道p是一個數組了,但還不知道陣列中的元素是什麼型別,這時,就要看左邊的星號 * ,星號 * 聲明瞭陣列中的元素是指標型別。現在,我們知道了陣列中的元素是指標型別,但還不知道那些指標是指向什麼型別的,這時int發揮作用了,它聲明瞭裡面的指標是指向int型別資料的,所以這個宣告的結果就是:聲明瞭一個數組 ,陣列有5個元素,每個元素都是指向整型資料的指標

到這裡,我們對這個語法(套路)有點頭緒了!

原來解釋宣告就是要先從名字p開始,然後從p的右邊開始看符號(因為優先順序高的符號 [ ]和 ( ) 是放在右邊的),如果有符號,剛和p先結合。看完右邊的符號(如果有的話)後,就決定了p是什麼。這時,就到p左邊的符號發揮作用了(左邊要麼是 * ,要麼就什麼都沒有)。最後發揮作用是則是型別說明符(因為它在最外面)。

總的來說,解釋宣告的套路就是不斷問什麼,然後從裡往外看符號來解答什麼的過程,說過無謂,再來看一段程式碼:

int *p();   //宣告1,宣告一個函式p,返回型別為指標型別
int (*p)(); //宣告2,宣告一個函式指標p,p指向一個函式

我們來按照套路來解釋這兩個宣告
宣告1:首先,要確定p是什麼?從p右邊的符號 ( ) 可以解得,p是一個函式。新的問題又來了,那函式p的返回值是什麼型別?從p左邊的星號 * 可以解得,函式p的返回值型別是指標型別。新的問題又來了,返回的指標指向的資料型別是什麼啊?這時,按照套路,應該繼續看外一層的右邊(因為看符號的順序為p的右左右左…,而且內層優先於外層),然而符號 ( ) 的右邊沒有符號了,轉而看向外一層的左邊。這時發現外一層的左邊是型別說明符int,因此解得,返回的指標指向的資料型別是int。整個宣告就是:宣告p為一個函式,函式的引數為空,函式的返回值為指標,指標指向的資料型別為int。

宣告2:由於這個宣告中,有個括號把星號 * 和 p包住了,所以 * 和 p屬於內層,而括號中p的右邊沒有符號,故 * 和p先結合,則聲明瞭p是一個指標,這回答了p是什麼。新的問題來了,p指向的是什麼?這時,內一層的符號看完了,繼續看外一層的右邊,外一層的右邊是符號( ) ,則解得p指向的是一個函式。然後再看外一層的左邊得,該函式的返回值是int型別。整個宣告就是:宣告p是一個指標,這個指標指向一個函式,這個函式的返回值為int

總結一下,解釋宣告的套路就是:先從最內層開始看符號,先從名字p的右邊開始看,再到左邊。然後跳到外一層的右邊開始,再到外一層的左邊開始看。不斷迴圈,直到沒符號為止

下面把這個套路應用到更加複雜的宣告中去。

如下面這個宣告:

char (*(*x())[])();

有沒有頭暈的感覺?不用怕,按照套路來很簡單的。
首先從最裡面開始,x的右邊是符號 ( ) ,所以x是一個函式。再看x的左邊,x的左邊是一個星號 * ,所以 函式x的返回值型別為指標型別。繼續跳到外一層的右邊,發現外一層的右邊是符號 [ ] ,所以指標指向的是一個數組。繼續看左邊,發現左邊是一個星號 * ,所以陣列中的元素的型別為指標型別。再看外一層的右邊,發現符號 ( ),所以陣列中的指標指向的是函式。最後看左邊的型別說明符char,所以函式的返回值型別為char型別。整個宣告就是:x是一個函式,函式的返回值型別是指標型別,這個指標指向的是一個數組,這個陣列是指標陣列(裡面的元素是指標),陣列中的指標是函式指標(指標指向函式),指標指向的函式的返回值型別是char型別。

再看這個宣告:

char (*(*x[3])())[5];

該宣告的結果為:x是一個數組,陣列有3個元素,元素的型別為指標型別,這些指標都是函式指標,所指向的函式的返回值為指標 ,返回的指標指向陣列,指向的陣列有5個元素,元素的型別為char型別。

到此為止,我們已經學會了複雜宣告的語法(套路)。這時我們也可以利用這個語法來宣告我們想要的變數。

2.3 使用複雜宣告

現在我們來宣告一些複雜的宣告。

比如,我們來宣告一個函式x,函式x的返回值為指標型別,該指標指向一個數組,陣列的元素型別為char
只要按套路反過來就行了:
1. 函式x:

x()
  1. 函式x的返回值為指標型別: 。
*x()

3.該指標指向一個數組:

(*x())[]

4.陣列的元素型別為char:

char (*x())[];

搞定!是不是很簡單?

這篇文章到這裡就結束了。這是我的第一篇部落格,如果有什麼錯誤,請在評論中回覆。希望這篇文章能幫助到大家更好地理解C語言中的宣告的語法。

.