C專家程式設計摘錄
-
c操作符的優先級
有時一些c操作符有時并不會像你想象的那樣工作。
下方表格将說明這個問題:
優先級問題 | 表達式 | 期望的情況 | 實際情況 |
---|---|---|---|
. 優先級高于* | *p.f | (*p).f | *(p.f) |
[ ]優先級高于* | int *ap[ ] | int (*ap)[ ] | int *(ap[ ]) |
函數()優先級高于* | int *fp() | int (*fp)(), fp是 int型函數的指針 | int *(fp( )),fp是傳回int型指針的函數 |
==和!=優先級高于位操作 | (val & mask != 0) | (val &mask) != 0 | val & (mask != 0) |
==和!=優先級高于= | c=getchar() != EOF | (c=getchar()) != EOF | c=(getchar() != EOF) |
算數運算符高于位移運算符 | msb << 4 + lsb | (msb << 4) + lsb | msb << (4 + lsb) |
, 優先級最低 | i = 1,2; | i = (1,2); | (i = 1),2; |
對于上表做出補充:
1.表中第二條。指向數組的指針(ptr to array of ints)和指針數組(array of ptrs-to-int)的差別.
指向數組的指針可以認為是二維數組,即int (*ap)[ ] 等價于 int ap[ ][ ].ap指針及每個ap(即ap+i)偏移指針指向一個一維數組(一串數)。
而指針數組是由連續多個int指針組成的數組,每個數組元素指向一個int 變量。
對于指針數組要多提一點,雖然聲明一個指針數組并不會被編譯器報錯,但是這是不安全的。 首先考慮這樣一種情況,聲明一個int 數組
int Na[5];
Na數組中的5個值都是随機值。當數組元素為指針時,情況也是相同的,即申請了一堆野指針。這将會導緻運作時錯誤。
實測圖:

可以看到,a[1]是一個空指針(nil),當使用者使用這個指針指向的記憶體時,将會導緻程式異常終止。
2. 一個非常妥善的對策是使用小括号将表達式包含起來(小括号優先級很高)。
------------
-
c操作符的結合性
每個操作符都有優先級和“左”或“右”的結合性。當操作符優先級不同時,求值順序取決于操作符優先級,而當操作符優先級相同時,這時将使用結合性。
1.除“位與”和“位或”外的所有操作符都遵守從右到左的結合性。(前者從左到右)
2.結合性目的在于表達式中操作符優先級相同情況下給出标準的運算順序。
3.當操作符的優先級和結合性組合情況較為複雜時,可以采用拆分表達式或者使用小括号。
eg:
int a,b=1,c=2;
a = b = c;
上述兩行代碼a,b,c結果為: a = 2,b = 2,c = 2;
---
-
c中的空格
c中的空格并非人們普遍認為的那樣不重要,可以随意增加或者減少。有時候空格會在根本上改變程式的原意。
1.反斜杠可以用來跳過一些字元,包括跳過一個新行(跳過回車符)。一個被反斜杠跳過回車的新行和他的上方未結束的行被認為是邏輯上的一行。但是如果上述情況下在反斜杠後面多打了一個空格(space),情況就不一樣了。(反斜杠跳過了空格,而不是回車)。
如圖:
2.現在這個問題主要由于ANSI 标準c中的“最大一口政策(maximal munch strategy)”。 考慮下述代碼:<<c專家程式設計>>筆記
int z = y+++x;
z = y + ++x;?還是 z = y++ + x;?
根據最大一口政策,答案是前者。
(注:最大一口政策:如果表達式下一個符号(token)有超過一種語義的可能性,編譯器将更願意咬掉更長的字元序列)
考慮下述代碼:
int z = y+++++x;
編譯器如何解析?根據maximal munch strategy,結果為 :
z = y++ ++ +x;
但不妙的是,編譯器不認識“++”,是以程式編譯錯誤。
3.第三種情況發生在類似這樣的一行代碼中:
int ratio = *x/*y;
編譯器如何處理這條語句?
結果如圖:
即“/*”被認為是一段注釋的開始,與我們期望的大相徑庭。而15行在加入空格(space)後則解決了這個問題。
4.其他情況(上述三種情況并不代表全部)。
----
-
C聲明中的優先級規則
c語言的聲明表達對于編譯器來說并沒什麼難度去解析,但對于學習C語言的程式員來說這種複雜性會造成一定的困擾。
舉個複雜聲明的例子:
char* const (next)();
下面介紹幾個較為有用的運算符優先級,并提供一個系統的面向于人的解析c複雜聲明的方法,最後附加一個c語言小程式,用于實作一個聲明的自動解析。
1.較為有用的優先級
聲明從名字開始解析,然後按照優先級順序繼續讀取下一個詞彙單元。
優先級順序:(從高到低)
1、小括号包含起來的聲明。
2、後繼操作符“()”表示一個函數,後繼操作符“[]”表示一個數組。
3、前驅操作符“*”表示一個“指向...的指針”。
4、如果const或volatile在類型辨別符(e.g.int,long,etc)旁,則它與類型辨別符比對。否則與它直接的左邊的星号比對。
2.系統的方法
處理順序: 對于整個表達式,采取從右向左的順序逐次擦除掉記号(token)。當所有記号被擦除,聲明的解讀過程也就完成了。 <-------- cha* const *(*next)(); step1:找到最左端的辨別符; 讀作:“辨別符 是...”; step2:如果右邊下一個記号是方括号“[possible-size]”; 讀作:“...的數組”; step3:如果右邊下一個記号是一個開放的小括号“(possible-parameter)”; 處理:一直讀到最近的右小括号比對。 讀作:”傳回...的函數“; step4:如果記号是左小括号 “(”; 處理:這個記号是當一個開放的小括号内記号被處理完後向左查詢到的。這時左右小括号裡的記号已經被擦除,繼續讀取右到小括号比對,擦除,傳回step2; step5:如果左記号是"const,volatile,*"三者中的一者 處理:繼續向左查詢記号,知道不再出現這三者中任何一個記号為止。從step4重新開始。 讀作:const:“常...”, volatile: “非常...”,* : “...指針”; step6:當記号是基礎的類型說明符時。 處理:結束 讀作:相應類型名。
下面以一個例子幫助了解:
(進行中的記号使用粗體)
待處理聲明 | 下一步處理 | 結果 |
---|---|---|
char* const *(*next) ( ) | step1 | 稱"next是...” |
char * const *(* ) ( ) | step2,step3 | 右小括号不比對,下一步 |
char *const *( * ) ( ) | step4 | *不比對,下一步 |
step5 | ”*“ 比對,稱”...的指針“,應用step4 | |
char *const *( ) ( ) | "(" 和 “)”比對,應用step2 | |
char *const * ( ) | step2 | 不比對,下一步 |
char *const * ( ) | step3 | 稱”傳回...的函數“ |
char *const * | ||
char *const | 稱”...的指針“ | |
稱”常...“ | ||
char * | ||
char | step6 | 稱“字元類型” |
最後連在一起表達:next是一個指向傳回指向常字元指針的函數的指針。:)
-
c中的指針和數組的關系
首先,如果你認為在c中指針和數組是等價的,那麼你很有必要向下看。
數組和指針本質是不同的,數組名代表的是一個位址,可以認為是一個隻讀的整數(const int),而指針是一個位址的位址(一個儲存位址的變量)。但是由于在很多情況下,數組可以轉換為指向數組首元素位址的指針,隻不過這種轉換是隐式的,但正因如此,才造成了我們的困惑。
在下面三種情況下,數組将轉換為指針:
1.數組作為函數的參數,将以指針的形式傳遞。
eg: void foo(int *arr) { ... }
2.數組作為表達式一部份參與求值。main { int arr[...]; foo(a); }
3.使用下标的數組名将會被轉換成一個指針加上偏移量。eg: int c = a[i]; //隐式轉換: int c = (a+i); //此時a是一個指針
eg: a[i] --->
(a+i) ;
注意:指針不會轉換成數組。
附:簡單的c語言聲明語義解析程式
點選此行顯示代碼
轉載請注明出處#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #define MAXTOKEN 100 #define MAXTOKENLEN 64 #define pop stack[top--] #define push(s) stack[++top]=s; typedef enum type_taag { IDENTIFIER,QUALIFIER,TYPE} tyag; struct token { char type; char string[MAXTOKENLEN]; }; int top = -1; struct token stack[MAXTOKEN]; //save tokens as a structure contians string struct token this_; //global structure contains string and type tyag classify_string(void) { char s = this_.string; if(!strcmp(s,"const")) { strcpy(s,"read only "); return QUALIFIER; } if(!strcmp(s,"volatitle")) return QUALIFIER; if(!strcmp(s,"signed")) return TYPE; if(!strcmp(s,"char")) return TYPE; if(!strcmp(s,"unsigned")) return TYPE; if(!strcmp(s,"short")) return TYPE; if(!strcmp(s,"int")) return TYPE; if(!strcmp(s,"long")) return TYPE; if(!strcmp(s,"float")) return TYPE; if(!strcmp(s,"double")) return TYPE; if(!strcmp(s,"struct"))return TYPE; if(!strcmp(s,"union")) return TYPE; if(!strcmp(s,"enum")) return TYPE; return IDENTIFIER; } void gettoken(void) { char p = this_.string; while((p = getchar()) == ' ') ; if(isalnum(p)) { // A-z, 0-9 while(isalnum(++p = getchar())) ; ungetc(p,stdin); p = '\0'; this_.type = classify_string(); return ; } if(p == '') { strcpy(this_.string,"pointer to"); this_.type = ''; return ; } this_.string[1] = '\0'; //'['']'(')' this_.type = p; return ; } void read_to_first_identifier(void) { gettoken(); while(this_.type != IDENTIFIER) { push(this_); gettoken(); } printf("%s is ", this_.string); gettoken(); //read next token after identifier } void deal_with_array(void) { while(this_.type == '[') { printf("array "); gettoken(); //read a number or ']' if(isdigit(this_.string[0])) { printf("0...%d ",atoi(this_.string)-1); gettoken(); //eat the ']' } gettoken(); //get next token past ']' printf("of "); } } void deal_with_function(void) { while(this_.type != ')') { gettoken(); //ignore the parameters of function } gettoken(); //eat the ')' printf("function returnning "); } void deal_with_pointer(void) { while(stack[top].type == '') { printf("%s ", pop.string); } } void deal_with_declarator(void) { switch(this_.type) { case'[':deal_with_array();break; case'(':deal_with_function();break; } deal_with_pointer(); //process the tokens which still in the stack while(top >= 0) { if(stack[top].type == '(') { pop; gettoken(); deal_with_declarator(); } else printf("%s ", pop.string); } } int main(void) { read_to_first_identifier(); deal_with_declarator(); printf("\n"); return 0; }