設計理念:
C語言的一個設計理念就是聲明變量和使用變量的形式應該是一致的
優點:聲明變量和使用變量時的運算符優先級是相同的
缺點:運算符的優先級是C語言過度解析的部分之一
術語:
變量聲明中使用到的符號的術語:(並不是所有的組合是合法的)
| 數量 | 名稱 | 舉例 |
| 0或更多 | 指針(pointer) | * |
| 一個 | 說明符(declarator) | identifier identifier[size] identifier(args) (declarator) |
| 0個或一個 | 初始化器(initializer) | = initial_value |
| 至少一個類型限定符 | 類型說明符 存儲類型 類型修飾符 |
void char short extern static register const volatile |
| 0個或一個 | 更多的說明符 | ,declarator |
| 一個 | 分號 | ; |
關於struct和union:
- 類型說明符是:struct {stuff...}
- 聲明形式:struct {stuff...} s;
- tag:為了簡寫,可以在struct后面加上結構體tag:struct struct_tag {stuff...},這樣就聲明了struct_tag代表具體的類型集合{stuff...},之后的聲明就可以使用struct struct_tag s;
關於參數傳遞的兩點說明:
- 某些書中會說"參數傳遞到調用函數的時候是從右到左壓到棧中",這種說法是不對的。參數傳遞的時候會盡可能使用寄存器,所以一個整數和一個只含有一個整數的結構體的傳遞方法是完全不同的,一個整數可能通過寄存器傳遞,結構體會通過棧傳遞
- 通常一個數組是不能直接通過賦值來傳遞整個數組的,或者被一個函數返回,但是通過把數組作為結構體的唯一一個成員就可以實現
雖然union具有和結構體類似的結構,但是它們有完全不同的存儲方式
- 結構體將每個成員存儲到其前一個成員后面
- union的所有成員都存儲在相同的起始地址,所以不同的成員是相互覆蓋的,同時只能有一個成員可以被存儲
union的一個明顯的問題就是存儲了一種類型但是用另一種類型取的類型安全問題,Ada語言主張在記錄中存儲說明字段來解決這個問題,但是C語言依賴於程序員能夠記住存了什么而不采取任何措施
union有兩個用途:
- 節約空間
- 所有的成員有相同的存儲空間大小的時候,就可以用不同的方式解析相同的二進制數據而不用顯式的進行類型轉換
聲明語句的解析:
C語言可以有非常復雜的聲明語句而讓人無法輕易的搞清楚到底定義了什么東西
有兩種解析方式:
方式一:優先級法則
- 聲明的解析從名稱開始,然后按照優先級規則繼續執行
- 優先級從高到低:
- 將聲明的各個部分組合在一起的括號
- 后綴操作符:指明一個函數的"()"和指明數組的"[]"
- 前綴操作符:指明是"指向..."的星號
- 如果"const"或"volatile"關鍵字和一個類型說明符相鄰,就應用到這個類型說明符;否則,如果應用到左邊緊鄰的"*"
方式二:狀態機規則
- 從最左側的標識符開始,"identifier是" "identifier is"
- 如果右側是"[]"就獲取,"一個...的數組" "array of"
- 如果右側是"()"就獲取,"參數為...返回值為...的函數" "function returning"
- 如果左側是"("就獲取整個括號中的內容,這個括號包含的是已經處理過的聲明,回到步驟2
- 如果左側是"const""volatile""*"就獲取,持續讀取左側的符號直到不再是這三個之中的,之后返回步驟4
- "const":"只讀的" "read only"
- "volatile":"volatile" "volatile"
- "*":"指向..." "pointer to"
- 余下的就是基本數據類型
舉例:char *(*c[10])(int **p);
- 按照優先級規則解析:
- c是一個...數組---c[10]
- c是一個指向...的指針的數組---*c[10]
- c是一個指向參數為...的返回值為...函數的指針的數組---(*c[10])()
- c是一個指向參數為整數的指針的指針的返回值為...函數的指針的數組---(*c[10])(int **p)
- c是一個指向參數為整數的指針的指針的返回值為指向...的指針函數的指針的數組---*(*c[10])(int **p)
- c是一個指向參數為整數的指針的指針的返回值為指向char的指針函數的指針的數組---char *(*c[10])(int **p)
- 按照狀態機規則解析:
- c是...---c---1->2
- c是一個...的數組---c[10]---2->3
- c是一個指向...的指針的數組---*c[10]---3,4,5->4
- c是一個指向...的指針的數組---(*c[10])---4->2
- c是一個指向參數為int的指針的指針返回值為...的函數的指針的數組---(*c[10])(int **p)---2,3->4
- c是一個指向參數為int的指針的指針返回值為...的函數的指針的數組---(*c[10])(int **p)---4->5
- c是一個指向參數為int的指針的指針返回值為指向...的指針的函數的指針的數組---*(*c[10])(int **p)---5->6
- c是一個指向參數為int的指針的指針返回值為指向char的指針的函數的指針的數組---*(*c[10])(int **p)---5->6
實現程序:
狀態機可以實現為自動翻譯程序:
https://github.com/biaoJM/translate-C-declaration-statement
typedef和#define:
1.宏定義的類型名和其他類型說明符一起執行定義,但是typedef只能使用它本身
#define peach int
unsigned peach i; /* works fine */
typedef int banana;
unsigned banana i; /* Bzzzt! illegal */
2.typedef的類型會實施到每個說明符,但是宏定義不會
#define int_ptr int *
int_ptr chalk, cheese;
// 結果為:
int * chalk, cheese;
導致chalk是int的指針類型,而cheese是int類型
typedef char * char_ptr;
char_ptr Bentley, Rolls_Royce;
Bentley和Rolls_Royce都是char指針類型
命名空間:
C語言的命名空間
- 標簽名,所有的標簽名的命名空間
- tags,對於所有的結構體、枚舉類和聯合體的tag具有的命名空間
- 成員名稱,對每個結構體、枚舉類或聯合體都有自己的成員命名空間
- 其他,其他名稱的命名空間
所以對聲明:
typedef struct baz {int baz;} baz;
這樣的定義是合法的:
struct baz variable_1; /*這里baz是定義的類型名*/
baz variable_2; /*這里baz是tag*/
對於這樣的定義:
struct foo {int foo;int foo2;} foo;
第一個foo是這個結構體的tag,第二個foo是一個結構體變量
sizeof(foo)的結果是變量foo的大小,所以如果聲明時這樣的:
struct foo {int foo;int foo2;} *foo;
sizeof(foo)返回的就是4而不是8,如果想要用tag獲取結構體的大小:sizeof(struct foo)——tag只有和struct關鍵字一起才起作用
而如果這樣定義:
typedef struct foo {int foo;int foo2;} foo;
那么就不能再用foo作為變量名,因為此時foo不再是tag而和變量有相同的命名空間
參考:
《expert C programming:deep C secrets》
Chapter 3. Unscrambling Declarations in C
