《C陷阱與缺陷》整理一

2021-06-26 06:24:40 字數 3521 閱讀 1127

1.詞法分析中的「貪心法」

c語言的某些符號,例如/、*和=,只有乙個字元長,稱為單字元符號。而c語言中的其他符號,例如/*和==,以及識別符號等都包含了多個字元,稱為多字元符號。當c編譯器讀入乙個字元'/'後又跟了乙個字元'*',那麼編譯器就必須做出判斷:是將其作為兩個分別的符號對待,還是合起來作為乙個符號來對待。c語言對這個問題的解決方案可以歸納為乙個很簡單的規則:每乙個符號應該包含盡可能多的字元。也就是說,編譯器將程式分解成符號的方法是,從左到右乙個字元乙個字元地讀入,如果該字元可能組成乙個符號,那麼再讀入下乙個字元,判斷已經讀入的兩個字元組成的字串是否可能是乙個符號的組成部分;如果可能,繼續讀入下乙個字元,重複上述判斷,直到讀入的字元組成的字串已不再可能組成乙個有意義的符號。

但是需要注意的是,除了字串與字元常量,符號的中間不能嵌有空白(空格符、製表符和換行符)。注意下面兩個例子:

a---b等價於a -- - b

a---b不等價於a - -- b

y = x/*p 不能實現預期的目的,可以修改為y = x/(*p)或y = x/ *p

2.字串常量

用雙引號引起的字串,代表的是乙個指向無名陣列起始字元的指標,該陣列被雙引號之間的字元以及乙個額外的二進位制值為0的字元'\0'初始化。

3.'yes'的處理

'yes'這種寫法的字元,根據編譯器的不同會得到不同的處理:大多數編譯器會理解為乙個整數值,由'y'、'e'、's'所代表的整數值按照特定編譯器實現中定義的方式組合得到;borland c++v5.5和lcc v3.6採取的做法是忽略多餘的字元,最後的整數值即第乙個字元的整數值;在visual c++6.0和gcc v2.95中採取的做法是,依次用後乙個字元覆蓋前乙個字元,最後得到的整數值即最後乙個字元的整數值。

4.c語言變數的宣告

任何c變數的宣告都由兩部分組成:型別以及一組類似表示式的宣告符。宣告符從表面上看與表示式有些類似,對它求值應該返回乙個宣告中給定的型別的結果。

float ff();

這個宣告的含義:表示式ff()求值的結果是乙個浮點數,也就是說,ff是乙個返回值為浮點型別的函式。

float *pf;

這個宣告的含義:*pf是乙個浮點數,也就是說pf是指向乙個浮點數的指標。

float *g();

這個宣告的含義:*g()是乙個浮點數,g()等價於上面的pf一樣理解,g是乙個函式,函式的值就是函式的返回值,所以g()返回的值應該是上例中的pf一致的型別,也就是乙個浮點型的指標。

float (*h)();

這個宣告的含義:(*h)()是乙個浮點數,然而(*h)()是乙個函式,所以h就是乙個函式指標,h指向的這個函式返回乙個浮點型的數。

5.強制型別轉換符的構建

從宣告變數出發,去掉宣告中的變數名和宣告末尾的分號,然後將剩餘的部分用乙個括號括起來即可。

float (*h)();-->(float (*)())

後面的強制型別轉換符的含義:返回值為浮點型別的函式的指標。

現在來分析(*(void(*)())0)()的含義

很明顯在常數0之前是乙個強制型別轉換符:(void(*)()),這個強制型別轉換符的含義是:返回值型別為void型別的函式指標。所以這整個表示式的含義就是,將0轉換為乙個返回值型別為void型別的函式指標,然後通過*運算子取得這個0所指向的函式,然後呼叫它。使用typedef來實現這個功能更為清晰:

typedef void (*funcptr)();

(*(funcptr)0)();

複雜一點的乙個例子:

void (*signal(int, void(*)(int)))(int);

含義:首先把signal(int, void(*)(int))看做乙個整體,這樣的話這個整體應該代表的是乙個函式指標,外層通過*運算子呼叫這個指標指向的函式。然而signal(int, void(*)(int))本身就比較好理解了,signal是乙個函式,接收的引數第乙個為int型別的值,另乙個引數為乙個函式指標。然而既然這個整體表示乙個函式指標,前面也提到過函式本身代表乙個值的話其實也就是說該函式的返回值是乙個什麼樣的值,所以signal函式返回乙個函式指標,該指標型別為void (*)(int)。那麼void (*signal(int, void(*)(int)))(int)宣告的signal如何呼叫呢?其實用法很簡單:

signal(int m, void(*ptr)(int);就可以實現呼叫,上面這個宣告方式的用處在於使得這個函式的返回值變得更加有意義,呼叫者可以定義乙個void (*)(int)型別的指標來接收signal函式的返回值。

這個功能同樣可以由typedef來實現,同樣會變得更為清晰:

typedef void (*handler)(int);

handler signal(int, handler);

6.當需要為乙個變數新增符號的時候可以採用下面的這種寫法:

int a = 3;

a = -a;

7.c語言的初始化例表中允許多書寫乙個逗號,這有什麼作用?

分析:這是為了詞法分析器的方便,這樣書寫之後,每乙個用來初始化的變數都是以逗號結束,編譯器處理起來更為方便。

8.c語言對陣列的名的理解

定義乙個陣列a之後,a除了作為運算子sizeof的引數之外,其他的所有情形中a都代表的是a中下標為0的元素的指標,這個在多維陣列的情況下仍然適用,因為在c語言中多維陣列是通過一維陣列模擬出來,只不過一維陣列的每個元素都可以是其他的各種型別的變數,當然也可以是另乙個陣列。所以如下的例子:

int a[3];

*a = 2;

*(a+1) = 3;

分別操作的是a陣列中的第0個元素和第1個元素,但是這樣操作起來往往比較麻煩,所以現在常用的寫法a[0]、a[1]、a[2]是上面的寫法的簡寫形式。*(a+1)等價於*(1+a),而*(a+1)的簡寫形式為a[1],而*(1+a)的簡寫形式為1[a],所以a[1]等價於1[a]寫法,很多編譯器都不會報錯,但是不推薦第二種寫法!!

9.c語言對陣列的名的進一步理解

下面的例子輸出會是多少?

int a[2][3];

printf("%d, %d", sizeof(a+1), sizeof(a[1]));

分析:先不說這個輸出結果為多少,首先來一步步分析需要輸出的內容,sizeof(a+1)編譯器在碰到這句話的時候,會把a當做乙個指標來處理,因為陣列+1沒有意義,只有陣列的指標+1才有意義,所以這裡測試出來的仍然只是乙個指標的大小,那麼如果是32位的pc的話輸出就會是4。再來分析sizeof(a[1]),這個測的是一維陣列a的第2個單元的大小,然而第二個單元裡面存放的是什麼呢?第二個單元裡面存放的是乙個3個單元的陣列,所以這個表示式測試出來的值為12。

答案:4, 12

10.malloc函式使用注意

malloc()函式分配記憶體失敗的時候會返回乙個空指標,在使用malloc的時候一定要判斷返回的值是否為空指標,否則會產生意想不到的後果。

《C陷阱與缺陷》整理二

1.陣列名作實參 在c語言中,我們沒有辦法將乙個陣列作為函式引數傳遞,如果我們使用陣列名作為引數,這個時候陣列名立刻會被轉換為指向該陣列的第乙個元素的指標。關於這一點的理解可以向前深入一步,比如定義的陣列為int a 3 那麼a作為引數傳遞之後會變為int 型別 如果定義的陣列為int a 3 4 ...

c陷阱與缺陷 陷阱

例1 if x y break 這就話的意思就是把y賦值x,判斷x是否為0,實則是在判斷y是否為0 例2 while c c t c n 這句話的意思就是 c t c n 賦值給c,而有 符本身就是不為0的數,所以這就是while 1 的意思。例3 int x 4,p new int p 2 cou...

C陷阱與缺陷

c語言對於符號 包含乙個或多個字元 的識別規則 每乙個符號應該包含盡可能多的字元。也就是說,編譯器將程式分解成符號的方法是,從左到右乙個字元乙個字元地讀入,如果該字元可能組成乙個符號,那麼再讀入下乙個字元,判斷已經讀入的兩個字元組成的字串是否可能是乙個符號的組成部分 如果可能,繼續讀入下乙個字元,重...