1. 要從邏輯上刪除一段C代碼,更好的方法是使用#if 指令。
2. 預處理指令:預處理器讀入源代碼,根據預處理指令對其進行修改,然後把修改過的源代碼遞交給編譯器。
3. 所有傳遞給函數的參數都是按值傳遞的。地址傳遞也相當值傳遞,因爲地址本身也可以作爲一個特殊的“值”,所以地址傳遞也是一種特殊的值傳遞。只是爲了強調其特殊性,故稱之爲“地址傳遞”。
4. NUL 是ASCII字符集中’\0’字符的名字,它的字節模式爲全0。
NULL指一個其值爲0的指針。它們都是整型值。其值也相同,所以他們可以互換使用。然而,還是應當使用適當的常量,因爲他能告訴閱讀程序的人不僅使用0這個值,而且告訴他們使用這個值的目的。
5. 符號NULL在頭文件stdio.h中定義,另一方面,並不存在預定義的符號NUL,所以如果你想使用它而不是字符常量’\0’,你必須自行定義。
6. 在使用形參時,如果該參數不能被修改,儘量使用const修飾,這可以很明顯的顯示這個意圖。
7. 一個C程序的源代碼可以保存在一個或多個源文件中,但一個函數只能完整地出現在同一個源文件中,把相關的函數放在同一個文件內是好的策略。
8. 缺省的int究竟是16位還是32位,或者是其他值,則由編譯器決定。通常這個選擇的缺省值是這個機器最爲自然的(高效)的位數。
9. 當可移植問題比較嚴重時,字符是否爲有符號就會帶來兩難的境地,最佳妥協方案就是把存儲於char型變量限制在singed char 和unsigned char的交集內,這可以獲得最大程度的可移植性,同時又不犧牲效率。並且,只有當char變量顯示聲明爲signed或unsigned時,纔對它執行算術運算。
10. 十進制整型字面值可能是int、long、或unsigned long。在缺省情況下,它是最短類型但能完整容納這個值。
11. C標準要求float 類型至少要能精確表示到小數點後6位,並且整數部分的表示範圍至少要達到10^-37 – 10^+37。float一般是 32位的。C 標準規定double類型的整數部分的最小表示範圍和 float 一樣,都是 10^-37到 10^+37,但是它要求double 類型的小數部分至少要能精確到小數點後 10 位。double通常是 64位的。
12. 浮點數字面值在缺省情況之下都是double 類型的,除非它的後面跟一個L或l表示它是long double 類型的值,或者跟一個F或f表示它是一個float類型的值。
13. signed 關鍵字一般只用於char類型,因爲其他整型類型在缺省情況下都是有符號的。
14. C語言編譯器一般並不檢查程序對數組下標的引用是否在數組的合法範圍內。如果下標值是從那些已知的正確的值計算而來,那麼就無需檢查它的值,如果一個用作下標的值是根據某種方法從用戶輸入的數據產生而來的,那麼在使用它之前必須進行檢測,確保他們位於有效的範圍之內。
15. int const *p:一個指向整型常量的指針,你可以修改指針的值,但你不能修改它所指向的值;
16. int *const p :一個指向整型常量的指針。此時指針是常量,它的值無法修改,但你可以修改它所指向的整型的值。
17. const常量只能用於允許使用變量的地方。
18. 有三個地方可以用於存儲變量:普通內存、運行時堆棧、硬件寄存器。凡是在任何代碼塊之外聲明的變量總是存儲於靜態內存中,也就是不屬於堆棧的內存,這類變量稱爲靜態變量。在代碼內部聲明的變量的缺省存儲類型是自動的,也就是說它存儲於堆棧中,稱爲自動變量。但是如果給在代碼塊內部聲明的變量加上關鍵字static,可以使他的存儲類型從自動變爲靜態。
19. 修改變量的存儲類型並不能修改該變量的作用域。函數的形參不能修飾爲靜態,因爲實參總是在堆棧中傳遞給函數,用於支持遞歸。
20. 關鍵字register可以用於自動變量的聲明,提示它們應該存儲於機器的硬件存儲器而不是內存中,這類變量稱爲寄存器變量。
21. 除非你對自動變量進行顯式初始化,否則當自動變量創建時,他們的值總是垃圾。
22. 如果整除運算的任意一方爲負數,運算的結果由編譯器定義的。
標準函數庫的算術運算頭文件中的div函數:
div_t div (intnumerator,int denominator);
div函數把它的第二個參數除以第一個參數,產生商和餘數,用一個div_t結構返回。這個結構包含下面兩個字段:
int quot; //商
intr rem; //餘數
但這兩個字段並不一定以這個順序出現。如果不能整除,商將是所有小雨代數商的整數中最靠近它的那個整數。注意‘/’操作符的除法運算結果並未精確定義。當/操作符的任何一個操作數爲負而不能整除時,到底商是最大的那個小於等於代數商的整數還是最小的那個大於等於代數商的整數,這取決於編譯器。
23. 標準說明無符號值執行的所有移位操作都是邏輯移位,但對於有符號值,到底採用邏輯移位還是算術移位取決於編譯器。所以有符號的移右位操作程序是不可移植的。
24. a<< -5:類似於這類的移位行爲是未定義的,所以它的值由編譯器決定。然而很少有編譯器設計者會清楚的說明如果發生這類情況將會怎樣,所以他的結果是不可測的。
25. ++ 或 – 操作符要求只能作用於一個左值。
26. 前綴形式的++:操作數的值被增加,表達式的值就是操作數增加後的值;
後綴形式的++:操作數的值仍被增加,但表達式的值是操作數增加前的值。
27. 前綴和後綴形式的增值操作符都複製一份變量值的拷貝。用於周圍表達式的值正是這份拷貝。前綴操作符在進行復制之前增加變量得值,後綴操作符在進行復制之後才增加變量的值。
28. 關係操作符:>、<</SPAN>、>=、<=、!=、== :這些操作符產生的結果都是一個整型值,而不是布爾值。如果兩端的操作數符合操作符指定的關係,表達式的結果是1,如果不符合,表達式的結果是0.
29. &&操作符的做操作數總是首先進行求值,如果它的值爲真,然後就對又操作數進行求值。如果左操作數爲假,那麼右操作數便不再進行求值。
30. 當float型值轉換爲整型值時,小數部分被捨棄(不是四捨五入)。如果浮點數的值過於龐大,無法容納於整型值中,那麼其結果將是未定義的。
31. 左值意味着一個位置,而右值意味着一個值。
32. 兩個相鄰的操作符的執行順序油它們的優先級決定,如果它們的優先級相同,他們的執行順序由它們的結合性決定。除此之外,編譯器可以自由決定使用任何順序對表達式進行求值,只要他不違背逗號,&&,|| 和 ?: 操作符的限制。
33. 不能簡單地通過觀察一個值的‘位’來判斷它的類型。爲了判斷值得類型(以及它的值),你必須觀察程序中這個值的使用方式。
即,不能通過一個值每個位中0和1的排列來判斷一個值得類型及其值。
比如:01100111011011000110111101100010 ,可以認爲它是1個32位整數(1735159650),可以認爲它是2個16位整數(26476和28514),可以認爲它是浮點數(1.116533*10^24),也可以它是4個字符(glob)。
該例選自《C與指針》6.2小節,值和類型。
34. 對指針執行加法或減法運算之後如果結果是指針所指的位置在數組第一個元素的前面或在數組最後一個元素的後面,那麼其效果就是未定義的。讓指針指向數組最後一個元素後面的位置是合法的,但對這個指針執行間接訪問可能會失敗。
35. 只有當兩個指針都指向同一個數組中的元素時,才允許從一個指針減去另一個指針,否則其結果未定義。
36. 標準允許指向數組元素的指針與指向數組最後一個元素後面的那個內存位置的指針進行比較,但不允許與指向數組第1個元素之前的那個內存位置的指針進行比較。
37. 聲明一個指針變量並不會自動分配任何內存。對指針進行間接訪問之前,指針必須進行初始化:或者使它指向現有內存,或者給它分配動態內存。對未初始化的指針變量執行間接訪問操作是非法的。
38. NULL指針就是不指向任何東西的指針。它可以賦值給一個指針,用於表示這個指針並不指向任何值。對NULL指針執行間接訪問操作的後果因編譯器而異,兩個常見的後果分別是返回內存位值零的值以及終止程序。
39. 函數的定義就是函數體的實現。函數體就是一個代碼塊,它在函數被調用時執行。
40. 當程序調用一個無法見到返回值類型的函數時,編譯器便認爲該函數返回一個整數值。對於那些並不返回整型值的函數,這種認定可能會引起錯誤。
41. 數組名相當於一個指針,但不是指針,它的類型是“指向元素類型的指針”。這個值是指針常量,而不是指針變量。在兩個場合下,數組名並不用指針常量來表示:當數組名作爲sizeof操作符或單目操作符&的操作數時。Sizeof返回整個數組的長度,而不是指向數組的指針的長度。取一個數組名的地址所產生的是一個指向數組的指針,而不是一個指向某個指針常量值的指針。
42. C的數組的下表引用和間接訪問表達式是一樣的。
int array[10];
int *ap = array+2;
ap[-1]:ap指向第3個元素(下標值爲2),所以使用偏移量-1則得到它的前一個元素,也就是array[1]。
43. 假定指針和下標這兩種方法都是正確的,下標絕不會比指針更有效率,但指針有時會比下表更有效率。
44. 但是,值得記住的是,效率並不是唯一的因素,通常代碼的簡潔性更爲重要。
45. 自動變量的位置位於堆棧中,則執行流每次進入它們所在的代碼塊時,這類變量每次所處的內存位置可能並不相同,所以自動變量在缺省情況下是爲初始化的。如果自動變量的聲明中給出了初始值,每次當執行流進入自動變量聲明所在的作用域時,變量就被一條隱式的賦值語句初始化。這條隱式的賦值語句和普通的賦值語句一樣需要時間和空間來執行。
46. 如果數組爲自動變量,初始化列表中可能有很多值,這就可能產生許多條賦值語句,對於那些龐大的數組,它的初始化時間及空間的消耗都是很可觀的。如果這不是必須的,就把數組聲明爲static,這樣數組的初始化只需在程序開始前執行一次。
47. 下標引用的優先級高於間接訪問。
48. 只要有可能,函數的指針形參都應該聲明爲const。
49. strlen的結果是個無符號數。
50. strcmp比較的結果如果想等則爲0,不等的話,標準並沒有說明一個具體值。它只是說明第一個字符串大於第二個字符串就返回一個大於零的值,如果第一個字符串小於第二個字符串就返回一個小於零的值。
51. strncpy把源字符串的字符複製到目標數組。它總是正好向dst寫入len個字符,如果strlen(src)的值小於len,dst數組就用額外的NUL字符填充,如果strlen(src)的值大雨或等於len,那麼只有len個字符被複制到dst中。這時,它的結果將不會以NUL字節結尾。
strncpy的原型:char *strncpy( char *dst,char const *src,size_tlen );
52. strncat總是在結果字符串後面添加一個NUL字符。
53. char *strtok(char *str,charconst *sep); sep參數是個字符串,定義了用作分隔符的字符集合。第一個參數是個字符串,它包含零個或多個由sep字符串中的一個或多個分隔符分隔的標記。strtok找到str的下一個標記,並將其用NUL結尾,然後返回一個指向這個標記的指針。
54. 字符分類函數(包含於頭文件 ctype.h),每個分類函數接受一個包含字符值的整型參數。函數測試這個字符並返回一個整型值,表示真或假。注意,標準並沒有指定任何特定值,所以有可能返回任何非零值。
55. 使用字符字符分類和轉換函數可以提高函數可移植性。直接測試或操縱字符將會降低程序的可移植性,
例如: if (ch >= ‘A’&& ch<= ‘Z’) //測試字符ch是否是一個大寫字符
這條語句在ASCII字符集的機器上能夠運行,但在使用EBCDIC字符集的機器上將會失敗。
if ( isupper(ch) ) //這條語句無論機器使用什麼字符集,它都能順利運行。
56. malloc所分配的是一塊連續內存。同時,malloc實際分配的內存有可能比你請求的稍微多一點,但是這個行爲是由編譯器定義的,所以你不能指望它肯定會分配比你的請求更多的內存。
57. 如果內存池是空的,或者它的可用內存無法滿足你的請求,在種情況下,malloc向操作系統請求,要求得到更多的內存,並在這塊新的內存上執行分配任務。如果操作系統無法向malloc提供更多的內存,malloc就返回一個NULL指針。因此,對每個從malloc返回的指針都進行檢查,確保它並非NULL是非常重要的。
58. realloc函數用於修改一個原先已經分配的內存塊的大小。如果它用於擴大一個內存塊,那麼這塊內存原先的內容依然保留,新增的內存添加到原先內存塊的後面,新內存並未以任何方法進行初始化。如果用於縮小一個內存塊,該內存塊尾部的部分內存便被拿走剩餘部分內存的原先內容依然保留。如果原先的內存塊無法改變大小,realloc將分配另一塊正確大小的內存,並把原先那塊內存的內容複製到新的塊上。因此,在使用realloc之後,你就不應該使用指向舊內存的指針,而是使用realloc所返回的指針。
59. 常見的動態內存錯誤:對NULL指針進行間接訪問操作、對分配的內存進行操作時越過邊界、釋放並非動態分配的內存、試圖釋放一塊動態分配的內存的一部分以及一塊動態內存被釋放之後被繼續使用。
60. 動態內存分配最常見的錯誤就是忘記檢查所請求的內存是否分配成功。
61. 動態內存分配的第二大錯誤來源是操作內存時超出了分配內存的邊界。
62. 不要訪問已經被free函數釋放了的內存。假定你對一個指向動態分配的內存的指針進行了複製,且這個指針的拷貝分佈於程序各處,你無法保證當你使用其中一個時它所指向的內存是不是已經被另一個指針釋放。所以,不用的指針立即賦空(NULL)。
63. 分配的內存在使用完畢後不釋放將引起內存泄漏。
64. 字符串常量的類型是“指向字符的指針”。所以表達式 *”xyz”的值是x。
65. 使用宏比使用函數在程序的規模和速度方面更勝一籌,因爲用於調用和從函數返回的代碼很可能比實際執行這個工作的代碼更大。
66. 宏與類型無關,一個宏可以處理各種類型的數據。
67. 宏的不利之處在於每次使用宏,一份宏定義代碼的拷貝都將插入到程序中。除非宏非常短,否則使用宏可能會大幅度增加程序的長度。
68. 避免用#define指令定義可以用函數實現的很長序列的代碼。
69. 在那些對表達式求值的宏中,每個宏參數出現的地方都應該加上括號,並且在整個宏定義的兩邊也加上括號。
70. 對於宏來說,只要合適就應該使用文件包含,不必擔心它的額外花銷。
71. fgetc 和 fputc都是真正的函數,但getc、getchar、和putchar都是通過#define指令定義的宏。
72. int fscanf(FILE *stream,charconst *format,……);
int scanf( char const *format,……);
int sscanf( char const *string,char const*format,……);
int fprintf( FILE *stream,char const*format,……);
int printf( char const *format,……);
int sprintf( char *buffer,char const*format,……);
爲了能讓這些函數正常運行,指針參數的類型必須是對應格式的正確類型。函數無法檢驗它們的指針參數是否爲正確類型,所以函數就假定他們是正確的,於是繼續執行並使用它們,如果指針參數的類型是不正確的,那麼結果值就會是垃圾,而相鄰的變量有可能在處理過程中被改寫。所以保證它們相互匹配是程序員的責任。
73. char *fgets (char *buffer, intbuffer_size, FILE *stream);
fgets從指定的stream讀取字符並把它們複製到buffer中。當它讀取一個換行符並存儲到緩衝區之後就不再讀取。如果緩衝區內存儲的字符數達到buffer_size -1個時它也停止讀取。雖然如此,但是並不會出現數據丟失的情況,因爲下一次調用fgets將從流的下一個字符開始讀取。
74. fgets函數如果在任何字符讀取前就達到了文件尾,緩衝區就未進行修改,fgets函數返回一個NULL指針。否則,fgets返回它的第一個參數。
75. char *fputs ( char const*buffer,FILE *stream);
向指定的文件寫入一個字符串。
這個字符串是逐字寫入的:如果它不包含一個換行符,就不會寫入換行符;如果它包含了好幾個換行符,所有的換行符都會被寫入。
如果寫入時出現了錯誤,fputs返回常量值EOF,否則它將返回一個非負值。
76. remov函數刪除一個指定的文件,如果當remove被調用時文件處於打開狀態,其結果是未定義的,取決於編譯器。
77. rename函數用於改變一個文件的名字,從oldname改爲newname。如果已經有一個名爲newname的文件存在,其結果取決於編譯器。如果這個函數失敗,文件仍然可以用原來的名字進行訪問。
78. 不要忘記檢查fopen函數的返回值。不要忘記使用fclose函數將打開的文件它關閉。
79. int abs( int value);
abs函數返回它的參數的絕對值。如果其結果不能用一個整數表示,這個行爲是未定義的。labs用於執行相同的任務,但它的作用的對象是長整型。
80. int rand(void);
void srand (unsigned int seed);
rand返回一個範圍在0和RAND_MAX(至少爲32767)之間的僞隨機數。當它重複調用時,函數返回這個範圍內的其他數。
但是隻調用這個函數,在產生一次隨機數序列之後,如果調用相同的程序,產生的隨機數就是上次產生的隨機數序列,除非產生比這個序列更多的隨機數,纔會在以前的序列後面出現新的隨機數。
int main()
{
int i=0;
while(i<10)
{
printf("%d\n",rand());
i++;
}
return 0;
}
運行一次之後,產生10個隨機數,然後不管運行多少次,都是該10個數;把while(i<10)改成while(i<20)後,程序會在上次10個數字後面再添加10個隨機數。
爲了避免這種程序每次運行時獲得相同的隨機數序列,我們調用srand函數。它用它的參數值對隨機數發生器進行初始化。一個常用的技巧是用時間作爲隨機數產生器的種子。
81. 使用數學運算函數時,不要忘記包含math.h頭文件。
82. 如果一個函數的參數不在該函數的定義域內,稱爲定義域錯誤。例如:sqrt(-5.0);因爲負值的平方根是未定義的。當出現一個定義域錯誤時,函數返回一個由編譯器定義的錯誤值,並且在errno中存儲EDOM這個值。
83. 頭文件errno.h是C語言C標準函式庫裏的標頭檔,定義了通過錯誤碼回報錯誤資訊的宏:errno宏定義爲一個int型態的左值,包含任何函式使用errno功能所產生的上一個錯誤碼。只有當一個庫函數失敗時,errno纔會被設置。當函數成功運行時,errno的值不會被修改。這意味着我們不能通過測試errno的值來判斷是否有錯誤存在。反之,只有當被調用的函數提示有錯誤發生時檢查errno的值纔有意義。
84. time函數返回當前的日期和時間。
time_t time ( time_t *returned_value);
如果參數是一個非NULL的指針,時間值也將通過這個指針進行存儲。如果機器無法提供當前的日期和時間,或者時間值太大,無法用time_t變量表示,函數就返回-1。