這本書短短的100多頁,很象是一篇文章。但是指出的很多問題的確容易出現在筆試的改錯題中
--------------------------------------------------------------------
第1章 詞法陷阱
1.1 = 和 ==
1.3 詞法分析的"貪進法則"
編譯器從左至右讀入字元,每個符號包含儘可能多的字元,直到不是字元為止
如:
a---b 等價於 a-- - b
a/*b 並不是 a/(*b), 而是/*當作注釋符
1.4 整型常量
0開頭的整數為8進位
如:
014 為8進位, 不要誤看作10進位
1.5 字元和字串
單引號 - ASCII字元,實際上是一個整數
雙引號 - 指向匿名的字元數組起始字元的指標,該數組以引用的字元和額外的'\0'初始化
另外,大多數編譯器允許一個字元常量中包含多個字元。Borland C++中只取第一個字元,而VC 6.0和GCC中則後面的字元依次覆蓋前面的字元,最後得到的是最後一個字元。
如:(GCC 3.5)
char ch='yes';
cout << ch;//output: s, but with warning
練習題:
1-1。某些C編譯器允許嵌套注釋。請寫一個測試程式,要求:無論對是否允許嵌套注釋的編譯器,該程式都能正常通過編譯(無錯誤訊息),但是這兩種情況下程式執行的結果卻不同。
1-3. n-->0的含義?
(n--) > 0 貪進法則
1-4 a+++++b的含義?
((a++) ++) + b
但是值得一提的是:現代編譯器中,此式子是非法的
為什嗎?
你可以查看operator++(int)的原型: const int operator++(int)
返回的為const型,且為a的臨時拷貝。之所以返回為const型,就是為了將返回作為左值,也就防止出現這類式子
總結:a++不能做左值
---------------------------------------------------------------------------------------
第2章 文法陷阱
2.1 理解函式宣告
換個角度理解聲明語句(declaration)
聲明語句構成:類型 + 一組類似運算式的聲明符(declarator)
float f, g; //運算式f, g求值為浮點數,即f, g為浮點型
float ff(); //運算式ff()求值是一個浮點數,即ff是一個傳回型別為浮點數的函數
float *pf; //*pf求值是一個浮點數,即pf是指向浮點型的指標
更複雜的,
float *g(), (*h)();
依據上述並且()優先順序大於*,很容易知道g是一個函數,傳回型別為浮點指標(float *)
h為一個函數指標,函數傳回型別為float
(float (*h)()) 是一個類型轉換符
再看:
(*(void(*)())0)()
實質上是:
void (*fp)(); //declare a function pointer
typedef void (*fp_type)();// for simplifying, otherwise always need void(*)()
conv_0 = (fp_type)0; // converse function 0 into function conv_o
(*conv_0)();//using the conversed function
再讓我們看看<signal.h>中聲明的signal函數
void (*signal(int, void(*)(int)))(int)
首先,用typedef簡化,
typedef void (*handler_type)(int)
得,void (*signal(int, handler_type))(int)
進一步 handler_type signal(int, handler_type);
2.3 作為語句結束的分號
1)多寫了分號
if(x[i] > big);
big = x[i]
這還是很容易辨識
再看
2)漏寫了分號if(n < 3)
return
logrec.date = x[0];
logrec.time = x[1];
logrec.code = x[2];
看出問題來了沒?
繼續看下面一個經典的:struct logrec
{
int date;
int time;
int code;
}
main()
{
//
}
註:這個問題筆試題已經出現過
2.4 swith語句
這個估計是老生常談了
也就是case後的break有無的問題了
首先要搞清楚一件事:你可以把(case:)當作語句的標號,就好像彙編中的標號一樣。switch之後徑直跳到匹配的case處順序執行下去,以後再碰到case則無視
當然,程式設計中有意不要break的除外
2.6 “空懸”else引發的問題
看下面代碼:if(x == 0)
if(y == 0) error();
else{
z = x + y;
f(&z);
}
這段代碼可能與你的本意大相徑庭,因為else與最近的if匹配
防止這類問題很簡單,只要每次使用if,else都用{}
---------------------------------------------------------
第3章 語義陷阱
3.1 指標和數組
C語言數組需要注意:
1)C語言只有一維數組,數組大小必須在編譯期確定為常數。二維數組是通過數組元素也為數組的一維數組實現
2)對於一個數組,只能做2件事情:確定數組大小,取得指向數組首元素的指標。其他相關操作,如下標運算, 都是通過指標進行
int a[3]; //數組元素為int型
struct
{
int p[4];
double x;
}b[17]; //數組元素為結構體
int calendar[12][31];
//12個元素的數組,每個元素又是31元素的數組
//並非:31個元素的數組,每個元素是12個元素的數組
記住:數組名是指向該數組首元素的指標
如,int a[11]; //那麼a的類型為 (int *)
int *ptr;
ptr = a;
但是ptr = &a;是非法的,這裡&a的類型為int (*)[],即指向數組的指標,大多數編譯期對這種操作,或者視為非法,或者讓其等於a
在C中,a+i和i+a的含義是一樣的,但後者不推薦
下面看多維陣列:
int calendar[12][31];
int *p;
int i;
我們很容易知道calendar[4]表示什麼含義:calendar[4]表示calendar數組的第5個元素,是12個有31個元素的數組之一。
sizeof(calendar[4])結果為31×sizeof(int)
此例中,calendar名字轉換為一個指向數組的指標,其類型為int (*)[31]
於是p=calendar; 是非法的
int (*monthp)[31];
monthp = calendar;//OK
calendar[month][day] = 0;
等價於
*(*(calendar+month)+day) = 0;
怎樣分析這個呢?
首先,calendar+month是指向12個元素之一的指標,對其解引用得到就是其元素(而元素是數組),所以*(calendar+month)是指向含31個元素的數組首元素的指標,再位移然後解引用即得到最終的int型元素
總結:
1)數組名表示指向首元素的指標,類型為元素類型的指標
2) 對數組名取地址,為指向數組的指標,類型為數組的指標
3.2 非數組的指標 - 字串
字串常量:代表一塊包含字串中所有字元加上額外一個Null 字元('\0')的記憶體區的地址。
一般字串常量用字元數組儲存的,且是唯讀。
字串操作函數:
size_t strlen(char *);//計算字串長度,直到遇到'\0'.且不包括'\0'
int strcpy(char * dest, const char *src);
int strcat(char *dest, char *src);
注意其中的輸出參數dest必須是預先分配好,且有足夠的空間能容納
3.3 數組作為函數參數
自動轉換成指標
3.6 邊界計算與不對稱邊界
這個主題值得探討
3.7 求值順序
C中只有四個運算子(&&, ||, ? :和,)規定了求值順序,對於其他運算子不要錯誤的假設求值順序,他們求值順序是未定義的。
如:
i = 0;
while(i < n)
y[i] = x[i++];
這裡y[i]的地址在i自增前被求值是沒有任何保證的
3.9 整數溢出
C語言中存在2類整數算術運算:有符號運算與無符號運算。
兩個無符號數運算不存在溢出。
算術運算中一個是有符號數,另一個是無符號數,則有符號數會轉換為無符號數,運算時溢出也不可能發生。
兩個有符號數運算,溢出有可能發生。並且溢出發生時,溢出結果是未定義的。
那麼如何檢測是否發生溢出呢?
看下面的方式:
int a, b;
if(a + b < 0)
//do something
這種方式是不可靠的,因為對溢出結果做的任何假設都是不可靠的
正確的方式:
#include <limits.h>
int a, b;
if((unsigned)a + (unsigned)b > INT_MAX)
//...
或者
if(a > INT_MAX - b)
//...
--------------------------------------------------------------------------------------
第4章 串連
4.2 聲明與定義
下面聲明語句:
int a;
如果出現在所有函數體(包括main函數)之外, 它被成為外部對象a的定義,並且其初始值預設為0
下面聲明語句:
int a = 7;
定義a的同時指定了初始值
下面聲明語句:
extern int a;
並不是a的定義,說明a是一個外部整型變數,它的儲存空間在程式的其他地方分配
典型情況:
//file1.c
int a = 7;
//file2.c
int a = 9;
這種情況一般在串連時會報錯,因為定義只能一次,聲明卻可以很多
4.3 命名衝突與static修飾符
static將變數或函數的範圍限定在一個源檔案中了
4.5 檢查外部變數