《C陷阱與缺陷》 Andrew Koenig 讀書筆記
附錄來自網絡
2.1 理解函數的聲明
2.1.1 如何聲明一個給定類型的變量
任何c變量的聲明都由兩部分組成:
類型
以及一組類似表達式的聲明符
float f,g; //f和g的類型為float
float ((f));//((f))的類型為浮點型
float ff(); //表達式ff()求值結果為float
float *pf; //*pf是一個浮點數,也就是pf指向一個浮點數的指針
float *g(),(*h)();
表示*g( )與(*h)( )是浮點表達式。
因為( )結合優于*,*g( )也就是*(g( ));
g是一個函數,返回值為float的指針
h是一個函數指針,h指向的函數的返回值為浮點類型
2.1.2 如何得到一個類型的轉換符
一旦知道了如何聲明一個給定類型的變量,那么該類型的類型轉換符就是:
把聲明中的變量名和聲明末尾的分號去掉,再將剩下的部分用一個括號整個“封裝”起來即可
float (*h)();
表示h是一個返回值為float的函數指針,因此,
(float (*)())
表示一個“指向返回值為浮點類型的函數的指針”的類型轉換符
2.1.3 分析(*(void(*)())0)();
上面的程序是調用首地址為0位置的例程。
- 第一步 前導知識
假定fp是一個函數的指針,如何調用fp所指的函數?(*fp)()
fp指向一個函數,*fp就是該指針指向的函數,(*fp)( )就是函數的調用了
注意:在ANSI C 標準中可以簡寫為fp( )
,但這只是一種簡寫形式
注意
*fp
兩側的括號,非常重要,因為()
的優先級高于*
,所以在沒有括號的情況下就變成了*(fp( ))
,由于fp()
是(*fp)()
的簡寫,故也變為了*((*fp)())
- 第二步 分析
(*0)();
上面的式子是錯誤的,因為*操作的對象必須是一個指針,而0
是一個常量
那么就需要將常數0類型轉換為函數的指針
指針實際上是一個4個字節用來保存地址的數,如果將0轉化為函數的指針,由于指針的值為0,故轉換后會變成指向0地址的函數的指針
由2.1.1可以知道,轉化為返回值為void類型的函數的指針加上
(void(*)( ))
即可
(* (void(*)()) 0 )()
- 進一步擴展
利用typede可以更加簡化函數的聲明
例如:void (*signal(int,void(*)(int)))(int)
可以簡化為:
typedef void (*HANDLER)(int);
HANDLER signal (int,HANDLER);
關于上面typedef的解讀,見下面的附錄
2.2 運算符的優先級問題
記住優先級很好,因為太多的括號會影響閱讀,如果記不住,還是加括號吧。
記住三點:
- 任何一個邏輯運算符的優先級低于任何一個關系運算符(加減乘除...)
- 位移運算符的優先級比算數運算符要低,但比關系運算符(與或非...)要高。
- 三目運算符優先級最低
2.3 注意語句結束的;
據說90%的錯誤都是因為;
2.4 switch 語句
注意其中的break;
語句。
漏掉后會造成不可預計的錯誤,當然有意的漏寫,可以實現特別的控制結構。
2.5 函數調用
不帶參數也需要加參數列表;
f();
而不是
f;
2.6 "懸掛"else引發的問題
這段代碼中的else實際作用是在第二個if之后。
建議:
使用{ }
哪怕只有一句執行語句。
附錄
關于typedef
在signal函數中有這樣的定義:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
此處由于加了typedef自定義了一個新類型sighandler_t,所以第二行的函數原型看起來順眼多了,形式跟int func(char c, int i)無異,但是如果看不懂typedef語句,這兩句話仍然是噩夢。
要理解typedef,只要記住一句話就差不多了,那就是:
typedef在語句中所起的作用只不過是把語句原先定義變量的功能變成了定義類型的功能而已
。我們只消看幾個例子立即明白。
例如語句 typedef int *apple; 理解它的正確步驟是這樣的:先別看typedef,就剩下int *apple; 這個語句再簡單不過,就是聲明了一個指向整型變量的指針apple (注意:定義只是一種特殊的聲明),加上typedef之后就解釋成聲明了一種指向整型變量指針的類型apple 。
現在,回過來看上面的這個函數原型 typedef void (*sighandler_t)(int),蓋住 typedef不看 ,再簡單不過,sighandler_t就是一個函數指針,指向的函數接受一個整型參數并返回一個無類型指針 。加上typedef之后sighandler_t就是一種新的類型,就可以像int一樣地去用它,不同的是它聲明是一種函數指針,這種指針指向的函數接受一個整型參數并返回一個無類型指針 。怎么樣?簡單吧。
例子:
再來做一個更酷的練習,請看:typedef char *(* c[10])(int **p);
去 掉typedef就變成char *(* c[10])(int **p)
,先不管這個語句有多難看,它一定是聲明了一個擁有10個元素的數組c對不對?okay沒什么了不起的,只不過這個數組c的元素有點特別,它們都 是函數指針,并且它們所指向的這些函數統統都接受一個二級指針然后返回一直指向字符型的指針。加上typedef之后,c就不是一個數組了,而是一種類型 了,什么類型現在你能說出來了吧。 _
說到typedef就不能不把它跟宏替換比較,typedef相對于宏替換是一種徹底的“替換”,#define之所以被稱為宏替換,是因為它就是簡單地照搬替換字符串。來看個例子:
例1 typedef int x[10];
例2 #define x int[10]
例1定義了類型x,此時我們就可以用它來定義別的變量了,比如x y; 此時y是一個擁有10個整型變量的數組,效果與語句int y[10]無異。typedef帶給我們的是一種徹底的封裝
。
例2用了宏定義的方式,將來在該宏的作用域范圍內的任何地方遇到的x都將被簡單地替換成int [10]。
(注意到,宏替換結尾是不帶分號的,不同于typedef語句
)
對于typedef和宏可以有以下總結:
1、宏定義可以擴展,徹底封裝的typedef不可以。
//以下代碼完全沒問題:
#define apple int;
unsigned apple i;
//以下代碼則完全行不通:
typedef int apple;
unsigned apple i;
2、在連續聲明幾個變量的時候typedef可以完全保證變量是同一種類型,而宏替換無法保證。
#define apple int *;
apple a, b; //a和b類型完全不同,a是指向整型變量的指針,b是整型變量。
typedef int *apple;
apple a, b; //a和b的類型完全相同,都是指向整型變量的指針。
永遠要記住的是,typedef定義的是一種類型而不是變量,不能指望用它來定義一個變量