標籤:
一、函數的形參的聲明
C 語言可以像下面這樣聲明函數的形參:
void func(int a[]){ // ...}
對於這種寫法,無論怎麼看都好像要向函數的參數傳遞數組。
可是,在 C 中是不能夠將數組作為函數的參數進行傳遞的。無論如何,在這種情況下,你只能傳遞指向數組初始元素的指標。
在聲明函數形參時,作為類型分類的數組,可以被解讀成指標。
void func(int a[]){}
可以被自動地解讀為
void func(int *a){}
此時,就算你定義了數組的元素的個數,也會被無視。
【要點】
只有在聲明函數形參的情況下,int a[] 和 int *a 才具有相同的意義。
下面是一個稍微複雜一點的形參聲明的例子:
void func(int a[][5])
a 的類型為“int 的數組(元素個數 5)的數組(元素個數不明)”,因此它可以解讀成“指向 int 數組(元素個數 5)的指標”。因此,上面的聲明本來的意思是:
void func(int (*a)[5])
【要點】
在下面聲明的形參,都具有相同的意義。
int func(int *a); /* 寫法1 */
int func(int a[]); /* 寫法2 */
int func(int a[10]); /* 寫法3 */
寫法 2 和 寫法 3 是寫法 1 的文法糖。
二、關於空的下標運算子[]
在 C 語言中,遇到以下情況,下標運算子[]可以將元素個數省略不寫。
對於這些情況,不同編譯器會有各自特別的解釋,所以不能作為普通的規則來使用。
(1)、函數形參的聲明
正如上節中說明的那樣,對於函數的形參,最外層的數組會被解讀成指標,即使定義了元素個數也會被無視。
(2)、根據初始設定式可以確定數組大小的情況
在下面的情況下,編譯器可以根據初始設定式來確定元素的個數,所以可以省略最外層數組的元素個數。
int a[] = {1, 2, 3, 4, 5};char str[] = "abc";double matrix[][2] = {{1, 0}, {0, 1}};char *color_name[] = { "red", "green", "blue"};char color_name[][6] = { "red", "green", "blue"};
注意:int a[]; 會報錯
在初始化 數組的數組 的時候,如果有初始設定式,貌似即使不是最外層的數組,編譯器也應該能夠確定其元素個數。可是,在 C 語言中,允許下面這樣不整齊的數組初始化,因此還是不能簡單地確認最外層數組以外的元素個數。
int a[][3] = { /* int a[3][3]的省略形式 */ {1, 2, 3}, {4, 5}, {6}};char str[][3] = { /* char str[3][5]的省略形式 */ "hoge", "hog", "ho"};
似乎可以考慮讓編譯器選擇一個最大值,但 C 的文法並沒有這麼做。
如果這麼做是為了排查程式員的編程失誤,那什麼沒有把上面“不整齊的數組”也規定為錯誤?對於這種現象,我至今百思不得其解(莫非只是因為疏忽?)。
順便說一下,在初始化上面這樣不整齊的數組的時候,沒有對應的初始設定式的元素會被初始化為 0。
(3)、使用 extern 聲明全域變數的情況
全域變數多個編譯單元(.c 檔案)中的某一個中定義,然後從其他代碼檔案通過 extern 進行聲明。
在定義的時候還是需要元素個數的,但是在使用 extern 進行聲明的時候,在串連的時候編譯器可以確定實際的數組大小,所以可以省略最外層數組的元素個數。
正如前面說明的那樣,只有在聲明函數形參的時候,數組的聲明才可以被解讀成指標。
像下面這樣進行全域變數聲明的時候,將數組和指標混在一起,除了程式不能正常運行外,編譯器通常也不會報告任何警告或者錯誤。這一點需要引起注意(如今的連結器,有時也會報錯)。
file_1.c......中
int a[100];
file_2.c......中
extern int *a;
補充: 聲明 和 定義
在 C 語言中,“聲明”在規定變數或者函數的實體的時候被稱為“定義”。
比如,像下面這樣聲明全域變數的行為,就是“定義”。
int a;
以下的 extern 的聲明,意味著“使在某處聲明的對象能夠在當前的地方使用”,因此它不是“定義”。
extern int a;
同樣地,函數的原型是“聲明”,函數的“定義”是指寫著函數的實際執行代碼的部分。
自動變數的情況下,區別定義和聲明是沒有意義的,因為此時聲明必然伴隨著定義。
《征服 C 指標》摘錄5:函數形參 和 空的下標運算子[]