開始讀《C專家編程》之前,有一個很擔心的問題:94年出的講語言的書,在現在(2012)還有多少是適用的。因此,一邊讀,一邊用VS2010做實驗。最後發現大部分內容都還在用。讀完後,覺得最精彩的部分有二:一是講解如何理解聲明,二是深入地講解數組名與指標。下文是將看書過程中所做的筆記進行的整理。
p.s: 以下代碼均在VS2010測試過
1. 使用無符號數時要特別注意(不推薦使用無符號數)當無符號數與有符號數在同一條運算式中出現時,有符號數會被轉換為無符號數。e.g:int feng = -1;unsigned int yan = 5;bool result = (feng < yan) ? true : false; //最後的結果會是false 原因是C語言在計算含有不同類型的運算式時,會將類型向上提升。在本例中,int被提升了unsigned int,從而使-1的補碼被解析為很大的整數
2. 相鄰的字串常量會被自動合并成一個字串e.g:char *str[] = {"feng" "yan", "zero"}; //"feng"和"yan"被合并成一個了:"fengyan"
3. 易出錯的優先順序.高於* e.g: *p.f 正確的理解:*(p.f)[]高於* e.g: int *ap[] 正確的理解:int *(ap[]) ap是個數組,其元素為int*函數()高於* e.g: int *fp() 正確的理解:int* fp() fp是返回int*的函數==和!=高於位操作符 e.g: val & mask != 0 正確的理解:val & (mask != 0)==和!=高於賦值符 e.g: c = getchar() != EOF 正確的理解:c = (getchar() != EOF)算術運算高於移位元運算 e.g: msb<<4 + lsb 正確的理解:msb << (4 + lsb)逗號運算子優先順序最低 e.g: i = 1, 2 正確的理解:(i = 1), 2
4. 理解聲明,定義,typedef語句的步驟a. 找標識符b. 找被括弧括起來的部分c. 找尾碼操作符,如果是(),則表示是函數;如果是[],則表示是數組d. 找首碼操作符,如果是*,則表示“指向XX的指標”e. 找const和volatile,如果const,volatile後面緊跟類型(如int,long),那麼它作用於類型,其它情況下,作用於它左邊緊鄰的項 e.g:int const * zero; //zero是一個指標,指向一個常量整形char (*zero)[20]; //zero是一個指標,指向一個有20個char元素的數組typedef void (*ptr_to_func)(int); //ptr_to_func是新類型名,這種類型是一個函數指標,指向接收一個int參數,並返回void的函數char* const * (*zero)(); //zero是一個函數指標,該函數無參數,並返回一個指標,返回的指標指向一個常量指標char* (*zero[10])(int **p); //zero是一個數組,元素是函數指標,其指向的函數授受一個二維指標,並返回一個指向char的指標void (*signal
(int sig, void (*func)(int)))(int); //signal是一個函數,該函數接收一個int,一個函數指標,並返回一個函數指標
5. 左值與右值左值通常表示儲存結果的地方(地址),其值在編譯時間可知右值通常表示
地址的內容,其值通常要到運行時才知道
6. 指標與數組名不等同的情況(定義為數組,卻聲明為指標,或者反過來)前提知識(假設有定義:int array[10], *ptr;):a. 使用數組名下標訪問(如:array[1]),會直接將數組名的
地址加上位移值作為變數的地址(即array[1]的地址)b. 使用指標下標訪問(如:ptr[1]),會先取指標指向的內容,然後將這個
內容加上位移值作為變數的地址(即ptr[1]的地址) 不等同的原因:當定義為數組,卻聲明為指標時,相當於用指標下標訪問的方法來解析一個數組名下標,即先取數組第0號元素的內容,然後將這個內容加上位移值作為變數的地址,從而訪問了不該訪問的東西。反之亦然。
7. 指標與數組等同的情況a. 編譯器將
運算式中的數組名當作指向該數組第0號元素的指標,下標當作指標的位移量,即array[i]會被當作*(array + i)
b. 編譯器將
函數參數聲明中的數組名當作指向該數組第0號元素的指標,即在函數內部得到的是指標,而不是數組名 基於a情況,可知這條謠言是假的(至少在一維數組中一定不成立):用指標迭代數組比用下標迭代數組更快 基於b情況,可解釋為什麼在傳遞數組後,不能用以下方法計算數組長度int ArrayLength(int arr[]) { return sizeof(arr) / sizeof(arr[0]); //傳回值必定是1,因為此時的arr是一個指標,而不是數組名} 注意b情況的將數組改寫為指標並不是遞迴定義的,e.g:實參 char zero[10][10] 被改寫為 char (*zero)[10],這裡將數組的數組改寫為數組的指標實參 char *zero[10] 被改寫為 char **zero,這裡將指標數組改寫為指標的指標實參 cahr (*zero)[10] 不改變,因為此時的zero是指標,而不是數組
8. interpositioninterposition指使用者定義的函數取代庫中聲明完全相同的函數,注意這不是指重載,而是指下面這種:void zero(); //user defined functionvoid zero(); //library function 出現interposition時,要特別注意以下情況:void zero(); //user defined functionint main() { zero(); //調用使用者定義的函數zero,而不是庫函數zero FengYan(); //
假設這是另一個庫函數,並且函數內調用庫函數zero,此時由於interposition,變成調用使用者定義的zero return 0;} 備忘:出現interposition時,在VS2010會出現warning: inconsistent dll linkage
9. 堆棧段作用a. 儲存局部變數b. 函數調用時,儲存有關的維護資訊c. 用作暫時儲存區。e.g: 計算一個很長的運算式時,會把部分結果先壓到堆棧中