天天看點

必須弄懂的495個C語言問題

1.1 我如何決定使用那種整數類型?

如果需要大數值(大于32, 767 或小于32, 767), 使用long 型。否則, 如果空間很重要(如有大數組或很多結構), 使用short 型。除此之外, 就使用int 型。如果嚴格定義的溢出特征很重要而負值無關緊要, 或者你希望在操作二進制位和位元組時避免符号擴充的問題, 請使用對應的無符号類型。但是, 要注意在表達式中混用有符号和無符号值的情況。

盡管字元類型(尤其是無符号字元型) 可以當成“小” 整型使用, 但由于不可預知的符号擴充和代碼增大有時這樣做可能得不償失。使用無符号字元型有所幫助;類似的問題參見問題12.1。

在選擇浮點型和雙精度浮點型時也有類似的權衡。但如果一個變量的指針必須為特定的類型時, 以上規則不再适用。

如果因為某種原因你需要聲明一個有嚴格大小的變量, 確定象c99 的<inttypes.h> 那樣用某種适當的typedef 封裝這種選擇。通常, 這樣做唯一的好原因是試圖符合某種外部強加的存儲方案, 請參見問題20.3。

如果你需要操作超過c 的内置類型支援的超大變量, 請參見問題18.17。

參考資料: [k&r1, sec. 2.2 p. 34]; [k&r2, sec. 2.2 p. 36, sec. a4.2 pp.195-6, sec. b11 p. 257]; [iso, sec. 5.2.4.2.1, sec. 6.1.2.5]; [h&s, secs. 5.1,5.2 pp.110-114]。

1.2 64 位機上的64 位類型是什麼樣的?

c99 标準定義了long long 類型, 其長度可以保證至少64 位, 這種類型在某些編譯器上實作已經頗有時日了。其它的編譯器則實作了類似longlong 的擴充。另一方面, 也可以實作16 位的短整型、32 位的整型和64 位的長整型, 有些編譯器正是這樣做的。

參見問題18.17。

參考資料: [c9x, sec. 5.2.4.2.1, sec. 6.1.2.5]

1.3 怎樣定義和聲明全局變量和函數最好?

首先, 盡管一個全局變量或函數可以(在多個編譯單元中) 有多處“聲明”, 但是“定義” 卻隻能允許出現一次。定義是配置設定空間并賦初值(如果有) 的聲明。最好的安排是在某個相關的.c 檔案中定義, 然後在頭檔案(.h) 中進行外部聲明, 在需要使用的時候, 隻要包含對應的頭檔案即可。定義變量的.c 檔案也應該包含該頭檔案, 以便編譯器檢查定義和聲明的一緻性。

這條規則提供了高度的可移植性: 它和ansi c 标準一緻, 同時也相容大多數ansi 前的編譯器和連接配接器。unix 編譯器和連接配接器通常使用“通用模式” 允許多重定義, 隻要保證最多對一處進行初始化就可以了; ansi c 标準稱這種行為為“公共擴充”, 沒有語帶雙關的意思。

可以使用預處理技巧來使類似define(int, i);的語句在一個頭檔案中隻出現一次, 然後根據某個宏的設定在需要的時候轉化成定義或聲明。但不清楚這樣的麻煩是否值得。

如果希望讓編譯器檢查聲明的一緻性, 一定要把全局聲明放到頭檔案中。特别是, 永遠不要把外部函數的原型放到.c 檔案中: 通常它與定義的一緻性不能得到檢查, 而沖突的原型比不用還糟糕。

參見問題10.4 和18.6。

參考資料: [k&r1, sec. 4.5 pp. 76-7]; [k&r2, sec. 4.4 pp. 80-1]; [iso, sec.6.1.2.2, sec. 6.7, sec. 6.7.2, sec. g.5.11]; [rationale, sec. 3.1.2.2]; [h&s, sec. 4.8pp. 101-104, sec. 9.2.3 p. 267]; [ct&p, sec. 4.2 pp. 54-56].

1.4 extern 在函數聲明中是什麼意思?

它可以用作一種格式上的提示表明函數的定義可能在另一個源檔案中, 但在extern int f();和int f();之間并沒有實質的差別。

參考資料: [iso, sec. 6.1.2.2, sec. 6.5.1]; [rationale, sec. 3.1.2.2]; [h&s,secs. 4.3,4.3.1 pp. 75-6].

1.5 關鍵字auto 到底有什麼用途?

毫無用途;它已經過時。參見問題20.32。

參考資料: [k&r1, sec. a8.1 p. 193]; [iso, sec. 6.1.2.4, sec. 6.5.1;]; [h&s,sec. 4.3 p. 75, sec. 4.3.1 p. 76].

1.6 我似乎不能成功定義一個連結清單。我試過typedef struct { char*item; nodeptr next; } *nodeptr; 但是編譯器報了錯誤資訊。難道在c語言中一個結構不能包含指向自己的指針嗎?

c 語言中的結構當然可以包含指向自己的指針; [k&r2, 第6.5 節] 的讨論和例子表明了這點。nodeptr 例子的問題是在聲明next 域的時候typedef 還沒有定義。為了解決這個問題, 首先賦予這個結構一個标簽(“struct node”)。然後,聲明“next” 域為“struct node *”, 或者分開typedef 定義和結構定義, 或者兩者都采納。以下是一個修改後的版本:

struct node {

char *item;

struct node *next;

};

typedef struct node *nodeptr;

至少還有三種同樣正确的方法解決這個問題。

在用typedef 定義互相引用的兩個結構時也會産生類似的問題, 可以用同樣的方法解決。

參見問題2.1。

參考資料: [k&r1, sec. 6.5 p. 101]; [k&r2, sec. 6.5 p. 139]; [iso, sec.6.5.2, sec. 6.5.2.3]; [h&s, sec. 5.6.1 pp. 132-3]。

1.7 怎樣建立和了解非常複雜的聲明?例如定義一個包含n 個指向傳回指向字元的指針的函數的指針的數組?

這個問題至少有以下3 種答案:

1. char *(*(*a[n])())();

2. 用typedef 逐漸完成聲明:

typedef char *pc; /* 字元指針*/

typedef pc fpc(); /* 傳回字元指針的函數*/

typedef fpc *pfpc; /* 上面函數的指針*/

typedef pfpc fpfpc(); /* 傳回函數指針的函數*/

typedef fpfpc *pfpfpc; /* 上面函數的指針*/

pfpfpc a[n]; /* 上面指針的數組*/

3. 使用cdecl 程式, 它可以把英文翻譯成c 或者把c 翻譯成英文:

cdecl> declare a as array of pointer to function returning

pointer to function returning pointer to char

char *(*(*a[])())()

通過類型轉換, cdecl 也可以用于解釋複雜的聲明, 指出參數應該進入哪一對括号(如同在上述的複雜函數定義中)。參見問題18.1。

一本好的c 語言書都會解釋如何“從内到外” 解釋和了解這樣複雜的c 語言聲明(“模拟聲明使用”)。

上文的例子中的函數指針聲明還沒有包括參數類型資訊。如果參數有複雜類型, 聲明就會變得真正的混亂了。現代的cdecl 版本可以提供幫助。

參考資料: [k&r2, sec. 5.12 p. 122]; [iso, sec. 6.5ff (esp. sec. 6.5.4)]; [h&s,sec. 4.5 pp. 85-92, sec. 5.10.1 pp. 149-50]。

1.8 函數隻定義了一次, 調用了一次, 但編譯器提示非法重定義了。

在範圍内沒有聲明就調用(可能是第一次調用在函數的定義之前) 的函數被認為傳回整型(int) (且沒有任何參數類型資訊), 如果函數在後邊聲明或定義成其它類型就會導緻沖突。所有函數(非整型函數一定要) 必須在調用之前聲明。另一個可能的原因是該函數與某個頭檔案中聲明的另一個函數同名。

參見問題11.4 和15.1

參考資料: [k&r1, sec. 4.2 p. 70]; [k&r2, sec. 4.2 p. 72]; [iso, sec. 6.3.2.2];[h&s, sec. 4.7 p. 101].

1.9 main() 的正确定義是什麼? void main() 正确嗎?

參見問題11.11 到11.16。(這樣的定義不正确)。

1.10 對于沒有初始化的變量的初始值可以作怎樣的假定?如果一個全局變量初始值為“零”, 它可否作為空指針或浮點零?

具有“靜态” 生存期的未初始化變量(即, 在函數外聲明的變量和有靜态存儲類型的變量) 可以確定初始值為零, 就像程式員鍵入了“=0” 一樣。是以, 這些變量如果是指針會被初始化為正确的空指針, 如果是浮點數會被初始化為0.0 (或正确的類型, 參見第5 章)。

具有“自動” 生存期的變量(即, 沒有靜态存儲類型的局部變量) 如果沒有顯示地初始化, 則包含的是垃圾内容。對垃圾内容不能作任何有用的假設。這些規則也适用于數組和結構(稱為“聚合體” ); 對于初始化來說, 數組和結構都被認為是“變量”。

用malloc() 和realloc() 動态配置設定的記憶體也可能包含垃圾資料, 是以必須由調用者正确地初始化。用calloc() 獲得的記憶體為全零, 但這對指針和浮點值不一定有用(參見問題7.26 和第5 章)。

參考資料: [k&r1, sec. 4.9 pp. 82-4]; [k&r2, sec. 4.9 pp. 85-86]; [iso, sec.6.5.7, sec. 7.10.3.1, sec. 7.10.5.3]; [h&s, sec. 4.2.8 pp. 72-3, sec. 4.6 pp. 92-3,sec. 4.6.2 pp. 94-5, sec. 4.6.3 p. 96, sec. 16.1 p. 386.]。

1.11 代碼int f() { char a[] = "hello, world!";} 不能編譯。

可能你使用的是ansi 之前的編譯器, 還不支援“自動聚集”(automatic aggregates,即非靜态局部數組、結構和聯合) 的初始化。參見問題11.28。

1.12 這樣的初始化有什麼問題?char *p = malloc(10); 編譯器提示“非法初始式” 雲雲。

這個聲明是靜态或非局部變量嗎?函數調用隻能出現在自動變量(即局部非靜态變量) 的初始式中。

1.13 以下的初始化有什麼差別?char a[] = "string literal"; char *p= "string literal"; 當我向p[i] 指派的時候, 我的程式崩潰了。

字元串常量有兩種稍有差別的用法。用作數組初始值(如同在char a[] 的聲明中), 它指明該數組中字元的初始值。其它情況下, 它會轉化為一個無名的靜态字元數組, 可能會存儲在隻讀記憶體中, 這就是造成它不一定能被修改。在表達式環境中, 數組通常被立即轉化為一個指針(參見第6 章), 是以第二個聲明把p 初始化成指向無名數組的第一個元素。

為了編譯舊代碼, 有的編譯器有一個控制字元串是否可寫的開關。

參見問題1.11、6.1、6.2 和6.6。

參考資料: [k&r2, sec. 5.5 p. 104]; [iso, sec. 6.1.4, sec. 6.5.7]; [rationale,sec. 3.1.4]; [h&s, sec. 2.7.4 pp. 31-2]。

1.14 我總算弄清除函數指針的聲明方法了, 但怎樣才能初始化呢?

用下面這樣的代碼

extern int func();

int (*fp)() = func;

當一個函數名出現在這樣的表達式中時, 它就會“蛻變” 成一個指針(即, 隐式地取出了它的位址), 這有點類似數組名的行為。

通常函數的顯示聲明需要事先知道(也許在一個頭檔案中)。因為此處并沒有隐式的外部函數聲明(初始式中函數名并非一個函數調用的一部分)。

參見問題1.8 和4.8。

更詳細的”必須弄懂的495個c語言問題“下載下傳位址http://share.eepw.com.cn/share/download/id/89420

繼續閱讀