《C陷阱與缺陷》整理二

2021-06-26 06:41:08 字數 3438 閱讀 1685

1.陣列名作實參

在c語言中,我們沒有辦法將乙個陣列作為函式引數傳遞,如果我們使用陣列名作為引數,這個時候陣列名立刻會被轉換為指向該陣列的第乙個元素的指標。

關於這一點的理解可以向前深入一步,比如定義的陣列為int a[3],那麼a作為引數傳遞之後會變為int *型別;如果定義的陣列為int a[3][4],那麼a作為引數傳遞之後被變為int (*)[4];如果定義的陣列為int a[3][4][5],那麼a作為引數傳遞之後會變為int (*)[4][5];後續的以此類推。為什麼可以這樣呢?因為c語言中的多維陣列都是利用一維陣列**出來的,即一維陣列的每乙個元素都可以是別的型別的資料單元,即便這個資料單元又是另乙個陣列,然而根據上面的觀點,一維陣列a在被作為引數傳遞的時候會自動退化為指向該一維陣列的第乙個單元的指標,所以如果第乙個單元是乙個一維陣列,那麼a就退化為乙個一維陣列指標,如果a的第乙個單元是乙個二維陣列,那麼a就退化為乙個二維陣列指標,所以上面的結論是不難得出的。

2.看下面的**片段輸出會是多少?

void print(int b)

int main(void)

分析:要弄清楚這段**片段的輸出,還是要清楚函式呼叫時候陣列的傳遞過程,上面第一點已經說過了,在傳遞引數的時候陣列已經自動被退化為指向其第乙個單元的指標,所以在函式傳遞的過程中相當於出現了這樣的乙個賦值的過程,int b = a或者更清楚一些int b = &a[0],但是這樣的語句編譯器會認為是乙個錯誤的語法!但是實際中我們經常可能會這樣來使用卻並沒有報錯,這是因為編譯器在這裡會將b強制做一次退化,退化為乙個int *的指標型別。所以上面的程式片段輸出內容顯而易見,輸出的就是乙個int型別的指標變數的大小,也就是4(32位系統)。

3.main函式引數的兩種形式

int main(int argc, char *argv)

int main(int argc, char **argv)

需要注意的是,前一種寫法強調的重點在於argv是乙個指向某陣列的起始元素的指標,該陣列的元素為字元指標型別。

4.修改字串常量

以下的這種寫法:

char *p = "xyz";

p[0] = 'a';

編譯的器件可能不會產生問題,但是執行的時候很可能會碰到類似於某位址不能為written這種提示,k&rc中對這種修改行為的說明是:試圖修改字串常量的行為是未定義的。雖然有些編譯器允許這樣的行為,但是這種寫法是不值得提倡的。

5.空指標

除了乙個重要的例外情況,在c語言中將乙個整數轉換為乙個指標,最後得到的結果都取決於具體的c編譯器實現。這個特殊情況就是常數0,編譯器保證由0轉換而來的指標不等於任何有效的指標,出於**文件化的考慮,常數0這個值經常用乙個符號來代替:

#define null 0

需要記住的是當常數0被轉換為指標使用時,這個指標絕對不能被解除引用(解除引用即是使用(*p)這類取該位址中內容的操作),換句話說,當我們將0賦值為乙個指標變數時,絕對不能企圖使用該指標所指向的記憶體中所儲存的內容。

6.c語言中「不對稱邊界」的好處

在c語言中定義了乙個陣列int a[10]之後,陣列的下標0~9為合法的下標,而下標10已經超出了陣列的範圍。這樣做的好處是什麼呢?

第乙個好處,請看下面的乙個例子:

for(i = 0; i < 10; i++)

a[i] = *p++;

如果使用者給出了begin(0)和end(10)的範圍之後要求對這之間的單元進行操作,如果使用者給定的begin和end是相同的話,上面這種寫法完全可以避免出現錯誤。同時要操作的單元個數可以通過end-begin簡單的就算出來,這樣做的前提就是使用者給出的begin和end都是遵守c語言的「不對稱邊界」使用方法。而如果不使用不對稱邊界時候(這時候陣列的下標為1~10合法)的諸如**:

for(i = 1; i <= 10; i++)

a[i] = *p++;

才可以完成對陣列的初始化或者遍歷等操作,這樣寫之後,實際操作的單元個數為10-1+1=10個,這樣的計算過程如果程式設計師在程式設計的時候忘了加上乙個1那麼很容易造成程式的bug。同時如果將1和10換成begin和end變數的話,那麼使用者在呼叫這個函式的時候傳遞的begin和end值就算是同乙個值,這段**也會操作到陣列中的a[begin]值,這個也會造成呼叫者使用的困難。

第二個好處是我們可以將&a[10]來作為乙個判斷條件,作為緩衝區或者陣列操作完成的乙個標誌,這在實際程式設計中也是相當方便的。雖然對a[10]的值進行操作是屬於非法的行為,但是在ansi中明確規定了&a[10]這種操作是合法的。

7.--n >= 0和n-- > 0

在大多數的c語言實現中,--n >= 0至少與等效的n-- > 0一樣快,甚至在某些c實現中還要更快,第乙個表示式--n >= 0的計算是首先從n中減去1,然後將結果與0比較;第二個表示式的計算則首先儲存n,然後從n中減去1,最後比較儲存值與0的大小。

8.求值順序

c語言中只有四個運算子(&&、||、?:、,)存在規定的求值順序,運算子&&和運算子||首先對左側的運算元求值,只有在需要的時候才會對右側的運算元求值。運算子?:有三個運算元,在a?b:c中,運算元a首先被求值,根據a的值在求運算元b或者c的值(b和c只有乙個表示式會被計算)。而逗號運算子,首先對左側的運算元求值,然後該值被「丟棄」,在對右側運算元求值,整個表示式的值是最右側表示式的值。

逗號運算子舉例:a = (1, 2, 3);

a最後被賦值為3。

注意:分隔函式引數的逗號並非逗號運算子,例如:f(x, y)中的求值順序是未定義的,而在函式g((x,y))中卻是確定的先x後y的順序,在後乙個例子中,函式g只有乙個引數,這個引數的值就是括號中逗號運算子的值。

注意:在c語言中其他所有運算子對其運算元求值的順序是未定義的。特別地,賦值運算子並不保證任何求值順序。如果在乙個表示式中出現對同一變數的多次使用中出現了++或者--等操作後果有時是不可預計的。例如:

y[i] = x[i++];

9.邏輯運算的結果

邏輯運算子的結果是乙個邏輯值,即真(1)或假(0),而邏輯判斷的時候通常約定將0視作假,非0視作真。所以!10表示式的值為假(0),因為10非0在進行非運算的時候被視作真,真的非即為假。

10.溢位

c語言中存在兩類整數算術運算,有符號數與無符號數運算。無符號數運算中沒有溢位的說法,然而有符號數操作就可能會發生溢位的情況,當乙個運算的結果發生「溢位」時,作出任何假設都是不安全的。當碰到可能溢位的情況應該採取的方法是將兩個運算元a和b都強制轉換為無符號整數:

if((unsigned)a + (unsigned)b > int_max)

complain();

此處的int_max是乙個已定義常量,代表可能的最大整數值。ansi c標準在中定義了int_max;如果在其它的c語言實現上,讀者可能需要自己重新定義這個值。

《C陷阱與缺陷》整理一

1.詞法分析中的 貪心法 c語言的某些符號,例如 和 只有乙個字元長,稱為單字元符號。而c語言中的其他符號,例如 和 以及識別符號等都包含了多個字元,稱為多字元符號。當c編譯器讀入乙個字元 後又跟了乙個字元 那麼編譯器就必須做出判斷 是將其作為兩個分別的符號對待,還是合起來作為乙個符號來對待。c語言...

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