徹底搞懂 C 語言指針聲明!

大家好,我是小北。

複雜類型說明

要了解指針,多多少少會出現一些比較複雜的類型。所以先介紹一下如何完全理解一個複雜類型。

要理解複雜類型其實很簡單,一個類型裏會出現很多運算符,他們也像普通的表達式一樣,有優先級,其優先級和運算優先級一樣。

所以總結了一下其原則:從變量名處起,根據運算符優先級結合,一步一步分析。

下面讓我們先從簡單的類型開始慢慢分析吧。

int p;

這是一個普通的整型變量。

p is int.

int *p

首先從 p 處開始,先與 * 結合,所以說明 p 是一個指針。然後再與 int 結合,說明指針所指向的內容的類型爲 int 型,所以 p 是一個指向整型數據的指針。

p is pointer to int.

int p[3];

首先從 p 處開始,先與 [] 結合,說明 p 是一個數組。然後與 int 結合,說明數組裏的元素是整型的,所以 p 是一個由整型數據組成的數組。

即:p is arry(size 3) of int.

int *p[3];

首先從 p 處開始,先與 [] 結合,因爲其優先級比高( [] 在 c 語言中屬於後綴運算符和 () 等同爲最高優先級),所以 p 是一個數組。然後再與 * 結合,說明數組裏的元素是指針類型。之後再與 int 結合,說明指針所指向的內容的類型是整型的,所以 p 是一個指向 int 的指針數組。

英文即: p is arry(size 3)  of pointer to int.

int (*p)[3];

首先從 p 處開始,先與 * 結合 (因爲 * 是被括號包圍的),說明 p 是一個指針。然後再與[] 結合 (與 "()" 這步可以忽略,只是爲了改變優先級),說明指針所指向的內容是一個數組。之後再與 int 結合,說明數組裏的元素是整型的,大小是 3,所以 p 是一個指向 int 數組(大小爲 3) 的指針。

英文即:p is pointer to arry(size 3) of int.

int **p;

首先從 p 開始,先與 * 結合,說明 p 是一個指針。然後再與 * 結合,說明指針所指向的元素還是指針。之後再與 int 結合,說明該指針所指向的元素是整型數據。由於二級指針以及更高級的指針極少用在複雜的類型中,所以後面更復雜的類型我們就不考慮多級指針了,最多隻考慮一級指針。

英文即:p is pointer to pointer to int.

int p(int);

從 p 處起,先與 () 結合,說明 p 是一個函數。然後進入 () 裏分析,說明該函數有一個整型變量的參數,之後再與外面的 int 結合,說明函數的返回值是一個整型數據。

英文即: p is function(int) returning int.

int (*p)(int);

從 p 處開始,先與指針結合,說明 p 是一個指針。然後與 () 結合,說明指針指向的是一個函數。之後再與 () 裏的 int 結合,說明函數有一個 int 型的參數,再與最外層的 int 結合,說明函數的返回類型是整型,所以 p 是一個指向有一個整型參數且返回類型爲整型的函數的指針。

英文即: p is pointer to function(int) returning int.

int* (*p(int))[3];

從 p 開始,先與 () 結合,說明 p 是一個函數。然後進入 () 裏面,與 int 結合,說明函數有一個整型變量參數。然後再與外面的 * 結合,說明函數返回的是一個指針。之後到最外面一層,先與 [] 結合,說明返回的指針指向的是一個數組。接着再與結合,說明數組裏的元素是指針,最後再與 int 結合,說明指針指向的內容是整型數據。所以 p 是一個參數爲一個整數據且返回一個指向由整型指針變量組成的數組的指針變量的函數。

p in function(int) returning pointer to arry(size 3) of pointer to int.

說到這裏也就差不多了。理解了這幾個類型,其它的類型對我們來說也是小菜了。不過一般不會用太複雜的類型,那樣會大大減小程序的可讀性,請慎用。這上面的幾種類型已經足夠我們用了。

總結

簡單總結下如何解釋複雜一點的 C 語言聲明(暫時不考慮 const 和 volatile):

  1. 先抓住 標識符(即變量名或者函數名)

  2. 從距離標識符最近的地方開始,按照優先順序解釋派生類型(也就是指針、數組、函數),順序如下:

    1. 用於改變優先級的括弧。

    2. 用於表示數組的 [],用於表示函數的 ()

    3. 用於表示指針的 *

  3. 解釋完成派生類型,使用 “of”、“to”、“returning” 將它們連接起來。

  4. 最後,追加數據類型修飾符(一般在最左邊,int、double 等)。

數組元素個數和函數的參數屬於類型的一部分。應該將它們作爲附屬於類型的屬性進行解 釋。

比如我們上面提到的一個例子

int (* p)(int);
  1. 抓住 p,即 p is

  2. 抓住改變優先級的 (),括號裏是 * ,也就是指針,即 p is pointer to

  3. 再看錶示函數的 (),即 p is pointer to function(int) returning, 記得 函數的參數類型屬於函數的一部分

  4. 最後看左邊的類型爲 int,即 p is pointer to function(int) returning int

翻譯爲中文就是: p 是一個指向 <參數爲 int 返回 int> 的函數指針。

實際上,實際開發中,根本用不上非常複雜的聲明,如果你寫了,絕對會被維護代碼的同事詛咒。

所以對於實在需要嵌套很多層的複雜聲明,請使用 typedef 來肢解複雜的聲明。

說白了,就是將複雜的聲明,先變爲一個個簡單的聲明,然後取一個別名。

還是拿這個例子來說:

int* (*p(int))[3];

一般來說,應該沒有多少同學能正確的識別出上面這個 p 到底是個什麼東西。

那根據我們上面的解釋,p 是一個函數,只是它的返回值是一個指針,指向的是 <int 指針變量組成的數組>

那我們就用 typedef 把 <int 指針變量組成的數組> 取一個別名:

typedef int* SB[3];

假設就叫 SB。

那麼現在那個複雜的聲明就變簡單了:

SB *p(int);

這一個大家應該都認識吧,

p 是一個函數,函數返回一個指針,指針的類型是 SB。。。。

還有比這個更復雜的聲明,比如 signal 函數。

C 庫函數 void (*signal(int sig, void (*func)(int)))(int) 設置一個函數來處理信號,即帶有 sig 參數的信號處理程序。

大家可以嘗試着看下這個函數怎麼讀,實際上拆解開了挺簡單的,就是需要你傳入一個回調函數,但是寫在一起就是特別複雜。

人類天生不適合解讀那麼複雜的聲明,所以那就分層,直到我們能一眼看出,那麼就 ok~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/wd0KzXIBnmdpyC_1jC7Psw