轉載:http://dsqiu.iteye.com/blog/1687944html
首先本文是對參考中三個鏈接的博客進行的整理,很是感謝三位博主的努力,每次都感嘆網友的力量實在太強大了……git
第一章 快速上手
1. 在C語言中用/*和*/來註釋掉這段代碼,這個實際上並非十分的安全,要從邏輯上刪除一段C代碼,最好的辦法是使用#if指令:
#if 0
Statement
#endif
2. 其餘語言中,無返回值的函數稱爲過程(procedure)。
3. 數組作參數的時候是以引用(reference)的方式傳遞的,即地址傳遞。而標量和常量都是傳值調用,被調用的函數沒法修改調用函數以傳值形式傳遞給它的參數,然而當被調用函數修改數組參數的其中一個元素時,調用函數所傳遞的數組就會被實際修改。
4. 字符是以一串NUL字節結尾的字符。NUL做爲字符串終止符,它自己並不被看做字符的一部分,NUL表示字符串結尾,NULL表示空指針。
5.當傳遞一個數組時,能夠無需指定數組的長度(若是須要長度,則須要再增長一個長度的參數)。算法
int read(int a[], int len); express
(在函數內部最好進行出錯檢查)編程
6.使用scanf函數應該注意:使用全部格式碼(除了%c以外)時,輸入值以前的空白(空格、製表符、換行符等)會被跳過,值後面的空白表示該值的結束,所以,用%s格式碼輸入字符串時,中間不能包含空白。數組
7.編譯器一般不對數組下標的有效性進行檢查。緩存
8.註釋是不安全的,是不容許嵌套的,老是與第一個*/相結合。安全
9.數據結構
int ch;異步
while((ch = getchar()) != EOF && ch != '\n');
ch被聲明爲整型,可是又用來讀取字符的緣由:
EOF是一個整型數值,它的位數比字符類型要多,把ch聲明爲整型能夠防止從輸入讀取的字符意外的解釋爲EOF,可是同時意味着接收字符的ch必須足夠大,足以容納EOF。
第二章 基本概念
1.C語言在實現的過程當中,存在兩種環境,一種是翻譯環境(源代碼被轉換爲可執行的機器指令),另外一種是執行環境(用於執行實際代碼),這兩種環境沒必要位於同一臺機器上,例如交叉編譯。
2.翻譯包括兩個階段:編譯與連接,其中編譯包括:預處理,解析,優化(可選)。
3.執行包括幾個階段:首先,程序必須再入到內存中。在宿主環境中(也就是具備操做系統的環境),這個任務由操做系統完成。那些不是存儲在堆棧中的還沒有初始化的變量將在這個時候獲得初始值。而後,便開始執行程序代碼。在絕大多數機器裏,程序將使用一個運行時堆棧(stack),它用於存儲函數的局部變量和返回地址。程序同時也可使用靜態(static)內存,存儲於靜態內存中的變量在程序的整個執行過程當中將一直保留它們的值。
環境:翻譯環境: 源代碼轉化成可執行的機器指令
執行環境:用於實際執行代碼
翻譯:源文件-〉目標文件-〉可執行文件(經過連接器將多個目標文件捆綁在一塊兒)
編譯過程:預處理器-〉源代碼通過解析產生目標代碼(這個過程當中是絕大多數錯誤和警告產生的地方)-〉優化器(就是對目標代碼進行進一步優化,使效率更高)
執行:首先,程序被加載到內存,那些不是存儲在棧中的未被初始化的變量將在這個時候被初始化;而後,程序的執行便開始了,負責處理一些平常事務,如收集命名行參數以便使程序可以訪問他們,並開始調用main函數;如今,開始執行程序代碼,在絕大多數機器裏,程序將使用一個運行時堆棧,用於存儲函數的局部變量和返回地址。程序同時也使用靜態內存,存儲與靜態內存中的變量在整個程序執行過程當中將一直保持不變。最後,程序終止,正常終止的話,是main函數返回。
4. 三字母詞:幾個字符組合起來表示另一個字符
??( [ ??< { ??= #
??) ] ??> } ??/ \
??! | ??' ^ ??- ~
print("You sure??!"); -→ You sure|
轉義字符:'\'(反斜槓)加上一個或者多個字符組成。
5.標識符是由大小寫字母、數字、下劃線組成,可是不能以數字開頭的,而且不能採用關鍵字來做爲標識符。
6.推薦的良好的程序風格(不必定要同樣,只是爲了程序的閱讀和維護)
(1)空行用於分隔不一樣的邏輯代碼段,按照功能進行分段;
(2)在括號和表達式之間留下一個空格,可使表達式更加突出;
(3)在絕大部分操做符的使用中,中間都隔以空格;
(4)嵌套於其它語句的語句將縮進;
(5)儘可能將註釋成塊出現;
(6)在函數的定義中,返回類型出現於獨立的一行中,函數的名字在下一行的起始處。
7.把一個大型程序放入一個單一的源文件中的優缺點:
優勢:容易找到修改函數的所在,連接所須要的時間會少點;
缺點:不易閱讀,不易維護,不利於多人合做。
第三章 數據
1. 變量三屬性:做用域(Scope),連接屬性(linkage),存儲類型(Storage Class)。
2. 一、C語言中僅有4種基本數據類型:整型、浮點型、指針、聚合類型(數組和結構等)。
變量的最小範圍:
char 0-127
signed char -127-127
unsigned char 0-255
short int -32767-32767
unsigned short int 0-65535
int -32767-32767
unsigned int 0-65535
整形數字以後添加字符L或者l,表示整數爲long type;添加字符U或u,表示爲unsigned type。長整型至少和整型同樣長,整型至少和短整型同樣長。可移植性問題:把存儲與char變量的值,限制在signed char 和unsigned char 二者的交集之中,這樣能夠得到最大程度的可移植性,又不犧牲效率。而且只有當char 類型顯示聲明爲signed 或 unsigned 時,纔對它執行算術運算。使用字符常量所產生的都是正確的值,因此它能提升程序的可移植性。
3. 若是一個多字節字符常量前面有一個L,表示是寬字符常量(wide character literal)。如L'X', L'love',當運行環境支持寬字符集時,就可使用它們。
4. 8進制在表示的時候須要前面加一個0,如067;C/C++不容許反斜槓加10進制數字表示字符,因此8進制數表示的時候,能夠省去零,如\67。
5. 枚舉類型的值,實際上爲數字;能夠對枚舉的符號名顯式的指定一個值,後面枚舉變量的值在此值基礎上加1。
6. 浮點數default類型爲double,後面跟L或l時是long double type,跟F或f時是float type。
7. 指針能夠高效的實現tree和list數據結構。
8. char *msg = "Hello World!";等價於:
char *msg; msg = "Hello World!";
第一種看似賦值給了*msg,但本質上是賦給了msg。
9. typedef最重要的用途是定義struct。
10. int const *pa;指向整型常量的pointer,能夠修改pointer value,但不能夠修改它所指向的value。
int *const pb;指向整型的常量pointer,沒法修改pointer value,但能夠修改它所指向整型value。
int const *const pc;pointer vale和指向的整型的value都不可被修改。
const修飾的對象不變,上例前兩個爲:*pa和pb,也就是說*pa和pb的內容不變。
11. #define MAX 50
int const max = 50;
這種情況下,#define更合適,它的使用範圍沒有被限定;而const變量只能被用於使用變量的地方。建立新的類型名的時候,應該使用typedef而不是#define,由於後者沒法正確處理指針類型。
- #define d_ptr_to_char char *
- d_ptr_to_char a, b;
這樣a是正確聲明瞭,可是b卻聲明爲了字符類型。
12. Internal連接屬性的標識符在同一源文件內的全部聲明中都指向同一實體。
External連接屬性的標識符不論聲明多少次,位於幾個源文件都表示同一實體。
具備external連接屬性的標識符,前面加上static關鍵字能夠是它的連接屬性變爲internal,static只對default屬性爲external的聲明有效果。
13. Static:當用於函數定義時,或用於代碼塊以外的變量聲明時,連接屬性從external變爲internal,標識符的存儲類型和scope不受影響,只能在源文件中訪問;當用於代碼塊內部的變量聲明時,static關鍵字用於修改變量的存儲類型,變動爲靜態變量,變量的連接屬性和做用域不受影響。
Type 聲明位置 Stack Scope 若是聲明爲static
全局 全部代碼塊以外 否 聲明處到文件尾 不容許從其餘源文件訪問
局部 代碼塊起始處 是 整個代碼塊 不在stack中,value在程序運行中一直保持
形參 函數頭部 是 整個函數 不容許
14.存儲變量的三個地方:普通內存,運行時堆棧,硬件寄存器(變量存儲類型是指存儲變量值的內存類型)。
變量的存儲類型取決於它們聲明的位置:
(1)凡是在任何代碼塊以外聲明的變量老是存儲在靜態內存中(程序運行前建立,而且若是沒顯式賦值則建立時缺省的賦於一個初始值0,在程序執行完畢後銷燬);
(2)在代碼塊內部聲明的變量是存儲在堆棧中(在程序執行到聲明自動變量的代碼塊時建立,在離開這個代碼塊時銷燬,若是加上關鍵字static能夠將其修改成靜態變量,可是不改變該變量的做用域,僅僅改變其存儲類型)。
第四章 語句
1. C沒有專門的賦值語句,而是採用表達式語句代替。
2. C沒有bool類型,而是用整數類型代替,零爲假,非零爲真。
3. 跳出多層loop的方法:
A. 使用goto語句。
B. 設置status flag,在每一個循環中都去判斷status flag。
C. 把全部loop放在一個單獨的函數中,使用return語句跳出loop。
else語句從屬於最靠近它的不完整的if語句。
4.break和continue
break用於永久終止循環,continue用於終止當前循環,這兩條語句出如今嵌套的循環內部,它只對最內層的循環起做用。
5.當循環體爲空的時候,單獨用一行來表示一條空語句是比較好的作法,這樣讓人一目瞭然。
6.for語句與while語句執行過程的區別在於出現continue語句時,在for語句中,continue語句跳過循環體的剩餘語句,直接回到調整部分(即for語句的第三個表達式);在while語句中調整部分是循環體的一部分,因此continue將會把它也跳過。
7.在while語句和do語句之間的選擇:當須要循環體至少執行一次時,選擇do語句。
8.對於switch語句:
switch(expression)
statement;
expression的結果必須是整型值。
switch語句執行時是貫穿全部的case標籤。
9.switch語句的case標籤只是決定語句列表的進入點,而不是劃分它們,要劃分它們,則須要break語句的幫助。
10.最好在switch語句中的最後一個case語句也加上一個break,這個有利於將來的維護,避免錯誤。
11.每一個switch語句只能出現一條default子句,最好在每一個switch語句中就加上一條default子句,這樣能夠避免一些沒必要要的錯誤。
12.C語言不具有任何輸入/輸出語句,I/O是經過調用庫函數來實現的,也不具有異常處理語句,也是經過調用庫函數來完成的。
第五章 操做符和表達式
1. 移位計數
int count_one_bits(unsigned value)
{
int ones;
for( ones = 0; value != 0; value = value>>1)
{
If(value%2 != 0)
ones = ones + 1;
}
return ones;
}
更有效率的方法:
int counter(unsigned value)
{
int counter = 0;
while(value)
{
counter++;
x = x&(x-1);
}
return counter;
}
此外x&(x-1)還能夠快速斷定x是否爲2^n。
2. 位操做符
指定bit置1: value = value | 1<<bit_number
指定bit清0: value = value & ~(1<<bit_number)
檢測指定bit: value & 1<<bit_number
3. sizeof():判斷操做數類型長度,以byte爲單位;操做數既可使表達式,也能夠是類型名。sizeof後若是是類型必須加括弧,若是是變量名能夠不加括弧。這是由於sizeof是個操做符不是個函數。若是是指針變量則返回4(指針變量自己存儲大小)。
sizeof()的操做數爲數組名時,返回的是該數組的長度。聯合類型操做數的sizeof是其最大字節成員的字節數。結構類型操做數的sizeof是這種類型對象的總字節數,包括任何墊補在內。判斷表達式的長度並不會對錶達式求值。sizeof 操做符不能返回動態地被分派了的數組或外部的數組的尺寸 。
4. 短路求值(short-circuited evaluation):"&&","||","?:",","
(L→R)左操做數的結果決定是否對錶達式進一步求值
5. 利用'.'和'->'訪問結構體的區別:
s爲struct變量時候,使用s.a訪問s中的成員a;
s爲指向struct變量的指針時,使用s->a訪問成員a。
7. 左值:表示一個存儲位置,能夠出如今賦值符的左邊或右邊;
右值:表示一個value,只能出如今賦值符的右邊。
8. 算術轉換(arithmetic conversion):操做數類型的轉換。(類型的提高)
經常使用方法是在執行運算以前把其中一個(或多個)操做數類型轉換爲左值的類型。
9. 複雜表達式的求值順序3要素:操做符的優先級,操做符的結合性,操做符是否控制執行順序(短路求值)。
優先級只對相鄰操做符的執行順序有效。
非法表達式的求值順序(規範沒有定義)由編譯器決定。
若是順序會對致使結果產生區別,最好使用臨時變量
10.C語言的算術操做符中只有%操做符要求兩邊都必須是整型數,其餘操做符都是既適用於浮點類型又適用於整數類型。
11.C語言中的左移操做時,右邊空出來的位均用0補齊,然而右移操做分爲兩類:邏輯右移和算術右移。 邏輯右移是左邊移入的位用0填充,算術移位是左邊移入的位由原先該值的符號位決定的,符號位爲1的移入的位均爲1,符號位爲0的移入的位均爲0。
注:無符號值執行的全部移位操做就是邏輯移位,而有符號值的移位操做是取決於編譯器;所以若是一個程序若是使用了有符號數的右移移位操做,它就是不可移植的。應該避免下面類型的移位操做,這個移位的位數也是由編譯器決定的。
12.C語言中位操做符要求操做數必須是整數類型;
13.關於位操做中的經常使用操做:
(1)置位:
value = value | 1 << bit_number;
(2)清零:
value = value & ~(1 << bit_number);
(3)測試:
value & 1 << bit_number;
14.賦值操做符的結合性是從右到左(求值的順序),在使用賦值運算時應該考慮截短問題的存在;
15.使用複合賦值符可以使源代碼更加容易閱讀和書寫;
16.sizeof是單目操做符而不是一個函數;
17.關於++操做符和--操做符的理解
關於前綴形式的++操做符和後綴形式的++操做符,在操做數以前的操做符在變量值被使用以前增長它的值,在操做數以後的操做符在變量值被使用後才增長它的值;對於前綴和後綴形式的增值操做符都複製一份變量值的拷貝,用於周圍表達式的值正是這份拷貝,所以這些操做符的結果不是被它們所修改的變量,而是變量值的拷貝,故它們不能像下面所示的形式進行操做:
a++ = 10; ++a = 10;
(a++和++a的結果都是a的拷貝,並非變量的自己,所以沒法進行賦值)。
18.條件操做符可以產生更小的目標代碼,從而提升效率。
19.逗號操做符可以使代碼更加易於維護:
逗號運算符 (,)
順序執行兩個表達式
expression1, expression2
說明
, 運算符使它兩邊的表達式以從左到右的順序被執行,並得到右邊表達式的值。 逗號運算符的優先級別在全部運算符中最低。, 運算符最普通的用途是在 for 循環的遞增表達式中使用。例如:
for (i = 0; i < 10; i++, j++)
{
k = i + j;
}
每次經過循環的末端時, for 語句只容許單個表達式被執行。, 運算符被用來容許多個表達式被看成單個表達式,從而規避該限制。
再以下面實例:
int x,y,z;
x=y=1;
z=x++,y++,++y;
printf("%d,%d,%d\n",x,y,z);
上面的表達式中應該等價於這樣的結合:(z=x++),y++,++y;若是這樣寫的話,則答案很清晰,爲:2,3,1。
例題1:(a = 3,b = 5,b+ = a,c = b* 5),求逗號表達式的值?
答案:40。前兩個表達式只是賦值,從第三個開始計算,b+=a,即b=b+a,即b=5+3,b=8,求最後一個表達式,c=b*5=8*5=40.由於逗號表達式的值是最後一個表達式的值,因此整個逗號表達式的值爲40,其餘各變量最後的值依次爲:a=3,b=8,c=40。
例題2:若已定義x和y爲double類型,則表達式:x=1,y=x+3/2的值是
A) 1 B) 2 C) 2.0 D) 2.5
分析:該表達式是一個逗號表達式,因此先運算x=1,結果變量x中的值爲1.0,而後運算y=x+3/2,其結果是變量y中的值爲2.0(這個運算過程可參閱本專題的「整數除法的注意事項」——整數相除,舍入法取整數部分),注意此時表達式y=x+3/2的值即等於變量y的值爲2.0。最後,整個逗號表達式的值應該等於最後一個表達式的值2.0,因此,正確答案是C)。
例題3:若t爲double類型,表達式t=1,t+5,t++的值是
A) 1 B) 6.0 C) 2.0 D) 1.0
分析:該題的表達式也是一逗號表達式,運算過程同例題1。須要注意的是,其中的第二個表達式(t+5)對整個表達式的運算結果不產生任何影響,由於它沒有改變變量x的值(x的值仍爲1.0),最後一個表達式(t++)的值爲變量x進行自增運算前的值1.0,因此整個表達式的值爲1.0。
例題4:有以下函數調用語句
func(rec1,rec2+rec3,(rec4,rec5));
該函數調用語句中,含有的實參個數是3。
20.C的整型算術運算老是至少以缺省整型類型的精度來進行的。
第六章 指針
1. 邊界對齊:boundary alignment(內存對齊)。
2. 不能簡單的經過檢查一個值的位來判斷它的類型。
數據的意義不在於它的類型,而在於它被使用的方式。
3. 指針的初始化用&操做符來完成,它用於產生操做數的內存地址。
4. 聲明的指針必須初始化纔可使用,否則不能肯定指針指向的地方。
5. 安全策略:讓函數返回獨立的值。首先是函數返回的status value, 用於判斷操做是否成功;其次是形參pointer,用於在操做成功時返回結果。
6. 指針變量能夠作左值,是由於它們是變量(存儲地址)。
7. 指針的強制類型轉換:
* 100 = 120; // 非法語句,由於間接訪問表達式(*)只能做用於指針類型表達式。
* (int *) 100 = 120; // 合法語句,把100從"整型"轉換爲"指向整型的指針"。(這僅僅是個例子)
8. 指針運算:
指針 +/- 整數:適用於指向數組中某個元素的指針,且要保證不越界。
指針 – 指針:適用於當兩個指針都指向同一數組中的元素時。
關係運算:<, <=, >, >=,適用於指向同一數組中的元素的兩個指針,表示的意義爲哪一個指針指向的數組元素更靠前或靠後。
指針運算只有做用於數組中,其結果纔是能夠預測的。
9.引發段錯誤緣由是引用非法地址,總線錯誤緣由是數據在內存中的存儲地址處在錯誤的邊界上。
10.對一個NULL指針進行解引用的操做是非法的,在對指針進行解引用操做以前,首先必須確保它不是NULL指針。
11.&ch可以做爲一個右值來使用,可是不能用來當左值來使用,至於緣由:咱們知道左值意味着位置,&操做符的的結果應該放在哪一個位置,確定位於一個地方,可是你沒法知道在什麼地方,所以這個表達式並未標識內存的位置,因此沒法做爲一個合法的左值。
由此可知,做爲一個左值,必需要有對它的存儲位置有個清晰的定義(關於左值,右值在上一章有提到)。
12.在函數內,若是傳進來一個指針,應該對其進行檢查是不是NULL指針;若是兩個指針所指向的不是同一個數組中的元素,那麼它們之間相減的結果是未定義的。
13.注意:
1. 能夠把指針初始化爲0、NULL或某個地址,具備值NULL的指針不指向任何值。
2. 當把0賦值給指針時,編譯器先把0轉換爲指向合適數據類型的指針。
3. 值0是惟一可以直接賦給指針變量的整數值。
4. 切忌使用未初始化的指針會給系統帶來隱藏的危害,一但指針指向非法區域,會形成系統崩潰。
第七章 函數
1. C函數的實現運用了堆棧。
函數在調用的時候,要爲被調用的函數分配內存空間(堆棧),相關寄存器的值也必須保存;在函數返回以後,釋放內存空間,恢復相關寄存器的原始值。
2. K&R C 中,形參的類型是以單獨列表的形式聲明的。
int * Find_int ( key, array, array_len ) int key; int array[]; int arrary_len;
{
// Function Body
}
這種聲明形式,新標準仍兼容。
3. 函數能夠分爲:真函數(有返回值,默認類型爲整型)和過程函數(無返回值)。
4. 函數在使用以前,必須進行"聲明"。
5. 無參函數: int * func (void); // 關鍵字void 提示沒有任何參數。
6. C函數的全部參數均爲傳值調用,函數得到的參數值只是實參的拷貝。
參數爲指針時傳遞的值爲指針對象的地址,這個行爲被稱爲傳址調用,也就是許多其餘語言所實現的var參數。
使用指針做爲函數參數對結構進行傳值,能夠提升程序的運行效率。
指針形參應儘量的聲明爲const,防止函數改變指針形參指向對象的值。
7. C能夠用於設計和實現抽象數據類型(ADT, abstract data type), 由於它能夠限制函數和數據定義的做用域。(Black Box: 黑盒的功能經過規定的接口訪問)
8. static的合理使用能夠限制對模塊的訪問,限制對那些非接口的函數和數據的訪問。static聲明的函數或數據,只能在文件內部被訪問。
9. 許多問題是以遞歸的形式進行解釋的,這只是由於它比非遞歸形式更爲清晰;但迭代的方式每每比遞歸更有效率。(用遞歸的方法去作斐波那契數列是很是浪費資源的<二的幾何次方>)
10. stdarg.h:實現了可變參數列表。
#define va_start(ap, parmN)(ap = ...)
#define va_arg(ap, type)(*((type *)(ap))++)
#define va_end(ap)
Stdarg.h中聲明瞭類型va_list和三個宏:va_start, va_arg 和 va_end。經過在函數中聲明va_list 變量,與三個宏配合使用,訪問參數的值。此宏不能夠判斷參數的個數和類型。
va_start(va_list, value); // value是參數列表中省略號錢最後一個有名字的參數。初始化過程是把va_list 指向可變參數的第一個參數,因此第一個參數必須是一個有命名的參數。
va_arg(va_list, type); // type是va_list中下一個參數的type。
va_end(va_list); // 訪問完全部的參數以後,調用va_end結束。
能夠在訪問參數的過程當中停止,但參數必須從第一個參數開始依次訪問。全部做爲可變參數傳遞給函數的值都會執行默認參數類型提高。
因爲參數列表中的可變參數部分沒有原型(type),因此,全部可變參數傳遞給函數的時候都將執行缺省參數類型提高。(默認參數類型提高:default argument promotion,在參數產地給函數以前,char和short提高爲int,float提高爲double…..)
11. 遞歸函數的兩個條件:(1).有限制條件(2).每次操做後愈來愈接近這個限制條件。
15. 遞歸函數運行設計一些開銷:參數必須壓到堆棧中,爲局部變量分配內存空間,應該慎重使用遞歸的方法,可用迭代法代替。
16.當程序調用一個沒法見到原型的函數時,編譯器便認爲該函數返回一個整型值。
17.函數傳遞的參數不能超過5個。
第八章 數組
1. 不可使用"="把一個數組的全部元素複製給另外一個數組。
2. int array[10];
int *ap = array + 2 ;
C的下標引用和間接表達式是同樣的。
ap[0],這個表達式是徹底合法的,這種狀況下對等的表達式爲*(ap+(0))。
2[array],這個表達式也是合法的,轉換爲間接表達:*(2+(array))。
3. 下標不會比指針更有效率,但指針有時會比下標更有效率。(效率:指針≥下標,如對數組進行循環賦值時。)當你根據某個固定的數目在一個數組中移動時,使用指針代碼將比使用下標產生更加有效率的代碼,當這個增量是1,而且機器具備地址自動增量模型時,這點表現更加突出。
4. int a[5]; // 初始化,分配了內存,*a徹底合法。
int *b; // 未初始化,*b指向內存中不肯定的位置。
聲明數組時,同時分配了內存空間,用於存儲數組元素;聲明指針時,只分配了存儲指針自己的內存空間,其指向的內存位置是未知的。
5. 函數形參中聲明爲指向const的指針:
A. 有助與使用者僅觀察該函數形參就能發現該數據不會被修改。
B. 編譯器能夠捕捉任何試圖修改該數據的意外錯誤。
C. 這類聲明容許函數傳遞const參數。
6. char message[] = "Hello"; // char數組初始化的一種方法,並不是字符串常量。
7. int matrix[3][10];
==> int (*p)[10] = matrix; // 指針p指向matrix的第一行。
==> int *pi = &matrix[0][0]; // 指針pi指向matrix第一行第一個元素。
==> int *pt = matrix[0]; // 指針pt指向matrix的第一行。
指向整型指針的指針(int **p),和指向整型數組的指針是不等價的(int (*p)[10])。
多維數組作函數參數時,必須顯式的指明第二維和之後維度的長度。這是由於多維數組的每一個元素自己是另一個數組,編譯器須要知道他的維數,以便爲函數形參的下標表達式進行求值。
8. char const keyword[] = {
"do", "for", "if", "register", "return"};
數組keyword的元素個數爲:sizeof(keyword)/sizeof(keyword[0]);。
9. 指針數組:數組的成員爲指針。聲明方式:int *pt[]。
10. 數組名是指向某種類型的指針常量,表示數組第一個元素的地址,數組名是一個常量是不能被修改的。
11. 聲明爲寄存器的指針比靜態內存和堆棧中的指針效率更高。
12. 若是你能夠經過測試一些已經初始化並通過調整的內容來判斷循環是否終止,那麼你就不須要一個單獨的計數器。
13. 函數中的形式參數傳遞的是數組時,不指定數組大小的緣由是由於數組傳遞的時候是以指針的形式傳遞的。
14. 多位數組的名稱表示的是,指向第一個數組的指針。
15. max[3,4]等價於max[3]。
16. 多維數組中初始化時,記得要把花括號加上(更容易區分,還有就是能夠給缺乏元素的初始化爲0)。
17. 只要有可能函數的形式參數都應該聲明爲const。
18.只有兩種場合下,數組名並不用指針常量來表示——就是當數組名做爲sizeof操做符或單目操做符&的操做數時,sizeof返回整個數組的長度,而不是指向數組的指針的長度,取一個數組名的地址所產生的是一個指向數組的指針。
19.除優先級之外,間接引用與下標引用徹底同樣;間接引用與下標引用能夠進行相互轉換。
20.數組與指針的區別:
(1)聲明一個數組時,編譯器將根據聲明所指定的元素數量爲數組保留內存空間,而後再建立數組名,它的值是一個常量,指向這段空間的起始位置;
(2)聲明一個指針變量時,編譯器只爲指針自己保留內存空間,它並不爲任何整型值分配內存空間,並且,指針變量並未被初始化爲指向任何現有的內存空間,若是是一個自動變量,它根本就不會被初始化。
21.若是在程序的執行每次進入該函數(代碼塊)時,每次都對數組進行從新初始化不是什麼有必要,就能夠把數組聲明爲static,這樣數組的初始化只需在程序開始前執行一次。
22.
char message[] = "hello";
char *message = "hello";
前者初始化一個字符數組的元素,然後者是一個真正的字符串常量,這個指針變量被初始化指向這個字符串常量的存儲位置。
23.在C中,多維數組的元素存儲順序按照最右邊的下標率先變化的原則,稱爲行主序。
24.二維數組名是一個指向數組的指針;
int matrix[3][10];
int *p = matrix;
int **p = matrix;
int (*p)[10] = maxtrix;
以上三種聲明只有一個是合法的,首先咱們知道matrix是一個指向數組的指針,第一個p是一個指向整型的指針,而第二個是爲指向整型指針的指針,只有三個是指向整型數組的指針,故第三個是合法的。
25.做爲函數參數的多維數組名的傳遞方式和一維數組名相同——實際傳遞的是個指向數組第一個元素的指針,可是二者之間的區別是:多維數組的每一個元素自己是另一個數組,編譯器須要知道它的維數,以便爲函數形參的下標表達式進行求值。
26.數組形參既能夠聲明爲數組,也能夠聲明爲指針,這兩種聲明形式只有當它們做爲函數的形參時纔是相等的。
第九章 字符串、字符和字節
1. strlen()返回的是無符號整型數。
無符號數之間進行的操做,結果仍是無符號數。儘可能不要在表達式中使用無符號數(可能致使表達式的結果不可預料);如:strlen(x) – strlen(y),他的結果永遠是大於等於0的。如要避免上述問題須要進行類型的強制轉換。
2. 字符串以NUL結尾,可經過判斷是否爲NUL計算長度。
3. 複製: char *strcpy( char *dst, char const *src ); // strlen(src)<strlen(dst)時dst數據丟失,strlen(src)>strlen(dst)時dst數據溢出。
鏈接: char *strcat( char *dst, char const *src );
比較: int strcmp( char const *s1, char const *s2 ); // 相等返回0
長度受限的字符串函數:
char *strncpy( char *dst, char const *src, size_t len ); // 若是strlen(src)小於len,dst數組會用NUL填充到長度len。要保證len小於等於strlen(dst).
char *strncat( char *dst, char const *src, size_t len );
int strncmp( char const *s1, char const *s2, size_t len );
4. 字符串查找:
char *strchr( char const *str, int ch ); // return第一次出現的位置
char *strrchr( char const *str, int ch ); // return最後一次出現的位置
char *strpbrk( char const *str, char const *group ); // 查找一組字符中任一字符第一次出現的位置
char *strstr( char const *s1, char const *s2 ); // 查找一個子串第一次出現的位置
size_t strspn( char const *str, char const *group ); // 第一次匹配的相對位置
size_t strcspn( char const *str, char const *group ); // 第一次不匹配的相對位置
char *strtok( char *str, char const *group ); // 函數會修改所處理的字符串
for( token = strtok(source, sep); token != NULL; token = strtok(NULL, sep)); // 若是strtok的第一個參數爲NULL,函數會從同一個字符串中上一個保存的位置開始繼續查找;若是沒有結果,返回NULL指針。
5. char *strerror( int error_number ); // strerror把一個錯誤代碼做爲他的參數,返回一個指向字符串的指針,該字符串用於描述這個錯誤。
6. 字符操做函數: iscntrl, isspace, isdigit, isxdigit, islower, isupper, isalpha, isalnum, ispunct, isgraph, isprint;使用這些函數能夠加強程序的可移植性。
7. 內存操做:如下函數遇到NULL字符不會中止
void *memcpy( void *dst, void const *src, size_t length );
void *memmove( void *dst, void const *src, size_t length );
void *memcmp( void const *a, void const *b, size_t length );
void *memchr( void const *a, int ch, size_t length );
void *memset( void *a, int ch, size_t length );
memcpy的效率高於memmove,但memmove在dst和src存儲位置發生重疊時,能夠繼續使用。
8.NUL字節是字符串的終止符,但它自己並非字符串的一部分,因此字符串的長度並不包括NUL字節。
9.用於複製字符串的strcpy原型:
char *strcpy(char *dst, char const *src);
因爲dst參數將進行修改,因此它必須是一個字符數組或者是一個指向動態分配內存的數組指針,不能使用字符串常量;使用這個函數時,必須保證目標字符數組的空間足以容納須要複製的字符串。
10.strcpy與strcat都返回一個第一個參數的一份拷貝,就是一個指向目標字符數組的指針,所以能夠嵌套調用這兩個函數。
11.在使用strncpy函數時,最好按以下的方法:
strncpy(buffer, name, size);
buffer[size - 1] = '\0';
這樣能夠保證buffer中的字符串是以NUL結尾的;
12.strncat與strncpy不一樣,strncat老是在結果字符串後面加上一個NUL字符。
第十章 結構和聯合
1. 下標操做和點操做具備相同的優先級,都是從左到右進行操做。可是點操做符的優先級高於間接訪問操做符。
2. "–>"操做符的左操做數必須是一個指向結構的指針。
3. 結構體自引用結構體是非法的,能夠經過引用指向結構體的指針解決。
4. 不徹底聲明:先聲明一個做爲結構標籤的標識符,而後能夠把標籤用在不須要知道這個結構長度的聲明中(如指針)。
struct B;
struct A {
struct B *pt;
};
Struct B {
sturct A *pt;
};
在A的成員列表中須要標籤B的不徹底聲明,A聲明以後,B的成員列表也能夠被聲明。
5. 內存對齊:編譯器爲一個結構變量分配內存空間時,需知足它們的內存對齊要求。(程序的可移植性)
結構體的成員聲明順序會影響到結構體所需的存儲空間。
struct A {char a; int b; char c;}; struct B {char a; char b; int c;};
在一個int長度爲4B,而且其存儲位置必須爲4的整數倍的機器上,sizeof(A)的長度爲12,sizeof(B)的長度爲8。
sizeof可以得出一個結構的總體長度,包括因內存對齊而跳過的字節。肯定結構成員的實際位置,可使用offsetof宏(stddef.h)。
offsetof(type, member); // type是結構的類型,member是成員名。
10. 結構體指針作函數參數時,加上const用於禁止修改指針指向結構的數據。
11. 位段:可以把長度爲奇數的數據封裝在一塊兒,節省存儲空間;能夠很方便的訪問一個整型值(register)。(位段是結構的一種)
位段成員必須聲明爲:int,signed int,unsigned int型,在成員名以後必須有一個冒號和一個整數,整數是該段所佔用位的數目。
struct RegisterA {unsigned aa:1; unsigned ab:2; unsigned ac:5;};
12. 聯合union:成員儲存在內存中的同一位置。聯合變量的初始化必須是第一個成員的類型,並且必須在一對"{}"中。
union {int a; float b; char c[4];}x = {5};
union的一個重要的做用是多選一。好比人的性別是兩種不一樣的屬性結構,在定義一我的的結構時就可使用聯合來選擇男女這兩種屬性結構
13. 若是你想在多個元文件中使用同一類型的數據結構,你應該把標籤聲明或typedef形式的聲明放在一個頭文件中。
14. 結構成員能夠是標量,數組,指針甚至是其餘結構。
15. 注意結構的不完整聲明
一、C提供兩種類型的聚合數據類型,數組和結構,數組是相同類型元素的集合,而使用結構可以把不一樣類型的值存儲在一塊兒;數組是經過下標引用或指針間接訪問元素的,而結構能夠經過成員名字來訪問的,固然結構也能夠經過指針來進行間接訪問的。
二、不一樣的結構聲明即便它們的成員列表相同也被認爲是不一樣的類型:
struct {
int a;
int b;
float c;
}x;
struct {
int a;
int b;
float c;
}y[20],*z;
對於以上的描述,這兩個聲明被編譯器看成兩種大相徑庭的類型,即便它們的成員列表徹底相同,所以變量y和z的類型和x的類型不一樣,所以下面這條語句不成立:
z=&x;
16.做爲函數參數的結構,若是採用傳遞結構,則須要複製整個結構的長度到堆棧中,再丟棄,這樣的效率比較低,而傳遞結構指針比結構小得多,所以可以提升效率,可是向函數傳遞結構指針也有不足之處,就是能夠對調用的變量進行修改(能夠用const聲明結構指針來防止)。
17.聯合的聲明與結構相似,聯合的全部成員引用的是內存中相同的位置,可用於某一時刻,只有一個字段被使用,提升效率。
18.若是聯合的各個成員具備不一樣的長度,聯合的長度就是它最長成員的長度。
19.聯合變量能夠被初始化,可是這個初始值必須是聯合第一個成員的類型,並且它必須位於一對花括號裏面。
例如:(把x.a初始化爲5)
union{
int a;
int b;
char c[4];
}x={5};
咱們不能把這個類量初始化爲一個浮點值或字符值,若是給出的初始值是任何其餘類型,它就會轉換爲一個整數賦給x.a。
第十一章 動態內存分配
1. void *malloc( size_t size ); // memory allocate
void free( void *pointer ); // free memory allocate
void指針表示能夠轉換爲任何其餘類型的指針。
free函數的參數能夠是NULL,要麼就必須是malloc、calloc、realloc返回的指針。釋放一塊內存空間是不容許的,動態分配的內存必須整塊的釋放;並且動態分配的內存必須在使用完以後被釋放,否則會引發內存泄漏(memory leak)
2. 在使用函數分配的內存前,必須先確保返回的指針不爲NULL。
3. void *calloc( size_t num_elements, size_t element_size ); // 元素的數量和大小
void realloc( void *ptr, size_t new_size );
calloc函數在分配內存空間以後會在返回指針以前把內存空間初始化爲0。
realloc函數不會改變原內存中存儲的數據。當用於擴大內存空間時,能夠在原內存空間以後增長;當原內存空間沒法修改時,realloc將會分配另一塊符合要求的內存空間,並把原內存空間的內容複製到新內存空間上。所以,在使用realloc以後,原有內存空間的指針不能繼續使用,應使用realloc返回的指針。realloc用於修改一個原先已經分配的內存塊的大小,在使用realloc以後,就不能再使用指向舊內存塊的指針,而是應該使用realloc返回的新指針,若是realloc的第一個參數是NULL,那麼它就跟malloc同樣;
4. a > b ? 1 : ( a < b ? -1 : 0); // 三種結果均可以兼顧到。
5. 動態內存分配容許程序爲一個長度在運行時才知道的數組分配內存空間。
6. malloc 函數用來實現從內存中提取一塊合適的內存,並向該程序返回一個指向這個內存的指針。這塊內存如今並無進行任何初始化。當一塊之前使用的內存沒有使用的時候,程序調用free函數將它歸還給內存池供之後使用。
malloc函數當沒有內存能夠分配時,就會返回一個null指針,因此對null指針的判斷很重要。
malloc和calloc的區別:一是,後者在返回指針以前先把內存初始化爲0;二是,calloc包括須要元素的數量和每一個元素的字節數,根據這個值,它能計算出到底須要分配多少內存。
realloc用於修改原先已經分配好的內存大小。能夠擴大也能夠縮小,擴大時,前面的存儲內容不變,後面的不被初始化;縮小時,尾部的內存便被砍掉。若是原先的內存不能改變大小,該函數將會從新分配一塊新的內存,將原先的內存中的內容複製過來。所以使用realloc 後就不能在使用指向舊內存的指針了,而是應該使用realloc 返回的的新指針。
7. 若是偶爾調用了malloc,程序將因爲語法錯誤而沒法編譯,在alloc中必須加入#undef指令,這樣他才能調用malloc而不至於出現語法錯誤。
8. 不要使用已經被釋放的內存。
9. 分配的內存在使用完畢後不進行釋放將會產生內存泄露。一個持續分配卻一點都不是放內存的程序最終將耗盡可用的內存。
10. 數組在聲明時,它所須要的內存在編譯時候就被分配;數組有其優勢與缺點:優勢在因而簡單,缺點是(1)沒法預知長度,數組沒法處理程序所須要使用的元素
11.動態分配內存的常見錯誤:
(1)對NULL指針進行解引用,即忘記檢查所請求的內存是否成功分配;
(2)操做內存時超出了分配內存的邊界;
(3)試圖釋放一塊動態分配的內存的一部分,所以傳遞給free的指針必須是一個從malloc、calloc、realloc函數返回的指針;
(4)一塊動態內存被釋放後被繼續使用;
(5)內存泄漏,所以應保證內存再也不使用時,釋放內存。
第十二章 使用結構和指針
1. 鏈表的存儲空間是動態分配的。
2. 單鏈表是一種使用指針來存儲值的數據結構。
3. 雙向鏈表:一個指針指向前一個鏈表節點,另外一個指針指向後一個鏈表節點。
4. 語句的提煉用來簡化程序,消除冗餘的語句。
5. 在鏈表中,每一個節點包含一個鏈表下一個節點的指針,連表最後一個指針字段的值爲null。爲了記住鏈表的起始位置,可使用一個根指針(root pointer),跟指針指向鏈表的第一個節點。注意跟指針只是一個指針,它不包含任何數據。
6. 鏈表就是一些包含數據的獨立數據結構(一般稱爲節點)的集合;鏈表中的節點可能分佈於內存的各個地方,不必定是物理上相鄰,單鏈表只能一個方向進行遍歷。
7. 鏈表中若是你想遍歷其餘的節點,你只能從根節點開始。能夠對鏈表進行排序。
8.語句提煉 :若是語句對if語句執行沒有影響咱們能夠將這個語句提早,若是語句在if執行後對這條語句沒有影響,能夠將這條語句放到後面。
9.不要僅僅以代碼的大小衡量代碼的效率。
10.在對鏈表的操做過程當中,應該注意如下幾點:
(1)動態分配內存時務必檢查是否分配成功(檢查返回值是否爲NULL);
(2)操做的過程當中,應該避免對NULL指針進行解引用,因此對於有可能出現NULL指針的地方都要進行判斷。
第十三章 高級指針話題
1. int* f, g; // 並無聲明兩個指針,星號只做用於f。
int *f(); // 函數操做高於間接訪問操做符,因此函數返回的是一個整形指針。
int *f[]; // 下標優先級更高,因此f是一個數組,元素是整型指針。
int (*f)(); // f爲一個函數指針
int (*f[])(); //聲明合法,數組f的元素是函數指針。
函數和變量都必須先聲明後使用,函數的類型能夠認爲是返回值的類型。
2. _cdecl:c declaration,表示默認方式:參數由右向左依次入棧,參數由調用者清除(手動清除)。
3. 函數指針的初始化:
int f(int);
int (*pf)(int) = &f;或者int (*pf)(int); pf=f;
函數名在使用時總被編譯器轉換爲函數指針,因此前一種初始化方式中的&操做符是可選的,&操做符只是顯式的說明編譯器將要執行的任務。
4. 回調函數:callback function,函數指針做爲參數被調用時,指針指向的函數被稱爲回調函數。回調函數分離實現了調用函數和被調用函數,調用函數不用關心誰是被調用函數(函數指針,回調函數),只需知道有一個具備特定原型和限制條件的被調用函數。
回調函數實現了調用函數的通用性,使得調用函數能夠支持多種數據類型和多種邏輯狀況。
調用函數常把與回調函數相關的參數類型聲明爲void*,表示爲指向未知類型的指針,加強了調用函數的泛用度。
5. 函數指針最多見的兩個用途是轉換表和做爲參數傳遞給另外一個函數。轉移表:實質上是一個函數指針數組,經過肯定數組中的元素來選擇調用相應的函數。使用轉移表時應該檢驗下標的有效性。
轉換表最好用個例子來解釋。下面的代碼段取自一個程序,它用於實現一個袖珍式計算器。程序的其餘部分已經讀入兩個數(op1和op2)和一個操做數(oper)。下面的代碼對操做符進行測試,而後決定調用哪一個函數。
switch( oper ){
case ADD:
result = add( op1, op2);
break;
case SUB:
result = sub( op1, op2);
break;
case MUL:
result = mul( op1, op2);
break;
case DIV:
result = div( op1, op2);
break;
......
對於一個新奇的具備上百個操做符的計算器,這條switch語句將很是長。
爲何要調用函數來執行這些操做呢? 把具體操做和選擇操做的代碼分開是一種良好的設計方法,更爲複雜的操做將確定以獨立的函數來實現,由於它們的長度可能很長。但即便是簡單的操做也可能具備反作用,例如保存一個常量值用於之後的操做。
爲了使用 switch 語句,表示操做符的代碼必須是整數。若是它們是從零開始連續的整數,咱們可使用轉換表來實現相同的任務。轉換表就是一個函數指針數組。
建立一個轉換表須要兩個步驟。首先,聲明並初始化一個函數指針數組。惟一須要留心之處就是確保這些函數的原型出如今這個數組的聲明以前。
double add (double,double);
double sub (double,double);
double mul (double,double);
double div (double,double);
......
double ( *oper_func[] )( double, double)={
add,sub,mul,div,...
};
初始化列表中各個函數名的正確順序取決於程序中用於表示每一個操做符的整型代碼。這個例子假定ADD是0 ,SUB是1,MUL是2,依次類推。
第 2 個步驟是用下面這條語句替換前面整條 switch 語句!
result = oper_func[ oper ]( op1,op2 );
oper從數組中選擇正確的函數指針,而函數調用操做符執行這個函數。
6. 命令行參數:int main( int argc, char *argv[] )
argc: argument count, argv: argument variables,char指針數組。
7. 字符串常量實質是一個指針。
"xyz" + 1; // 字符串"xyz"的地址加1。
*"xyz"; // 表達式結果爲'x'。
"xyz"[2]; // 表達式結果爲'z'。
8. 函數只能返回標量值,不能返回數組。
9. 對函數指針進行操做以前,必須把它初始化爲指向某個函數,函數指針的初始化也能夠經過賦值操做進行完成;在函數指針的初始化以前,具備函數的原型是很重要的。
10. 把具體操做和選擇操做份開始一個良好的程序設計方案。
11. 只有當確實須要時,才應該使用多層間接訪問,否則程序將會變得更龐大,更緩慢而且難以維護。
12. 不一樣尋常的代碼應該加上相應的註釋。
14.調用函數:
int ans;
ans = f(25);
ans = (*pf)(25);
ans = pf(25);
第一種調用函數過程:首先函數名f被轉換爲一個函數指針,該指針指定函數在內存中的位置,而後函數操做符調用該函數,執行開始於這個地址的代碼;
第二種調用函數過程:執行函數操做符以前,將函數指針轉換爲函數名,其後的過程與前者同樣;
第三種調用函數過程:省略了將函數名轉換爲函數指針,直接執行開始與這個地址的代碼。
第十四章 預處理器
1. 預約義符號:
符號 示例 含義
__FILE__ "name.c" 進行編譯的源文件名
__LINE__ 25 文件當前行的行號
__DATE__ "Jan 31 1997" 文件被編譯的日期
__TIME__ "18:04:30" 文件被編譯的時間
__STDC__ 1 若是編譯器遵循ANSI C,值就爲1,不然未定義。
Note:先後各2個下劃線。
2. #define reg register
#define do_forever for(;;)
#define CASE break; case
不要在宏定義的末尾加上分號,這會破壞代碼的可閱讀性。
3. 宏僅僅是替換。定義宏時要使用括號:替換的數據使用括號,整個宏使用括號。宏適於類型無關的,宏的命名約定:一種方式是都大寫,
4. 宏不能夠出現遞歸,即不可自我調用。
5. "#argument"結構被預處理器翻譯爲:"argument",即轉換爲字符串。
"##"結構被預處理處理爲把它兩邊的符號鏈接成一個符號。
#define paster(n) printf("token"#n"=%d\n", token##n)
int token9 = 10;
paster(9); // Result show in Screen: "token9=10"
6. 預處理移除指令:#undef symbol
7. 條件編譯:
#if constant-expression
Statements
#elif constant-expression
Statements
#else
Statements
#endif
是否認義symbol:
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
在"#define symbol"中雖然symbol的值是一個空字符而不是1,可是symbol被定義。
8. #error指令用於生成錯誤信息。
#error text of error message
#line指令通知預處理器number是下一行輸入的行號,若有string,則會把string做爲當前的文件名。(修改了__LINE__和__FILE__)
#line number "string"
#progma指令用於支持因編譯器而異的特性。
9. c與處理器要作的事情:刪除註釋,插入#include包含的內容文件的內容,定義和#define指令定義的符號以及肯定代碼的部份內容是否應該根據一些條件編譯指令進行編譯
10. #define的基本用法:
#define name stuff
使用#define指令,能夠把任何文本替換到程序中;(若是定義中的stuff很是長,能夠分紅幾行,除了最後一行外,每行的末尾都要加一個反斜槓,,而且不要加上;號)。
11.#define定義符號和宏的三個步驟:
(1)在調用宏時,首先對參數進行檢查,看看是否包含了任何由#define定義的符號,若是是,它們首先替換;
(2)替換文本隨後被插入到程序中原來文本的位置,對於宏,參數名被它們的值所替代;
(3)最後,再次對結果文本進行掃描,看看它是否包含了任何由#define定義的符號,若是是就重複上述處理過程。
在這個過程當中,應注意如下問題:
(1)宏參數和#define定義的能夠包含其餘#define定義的符號,可是宏不能夠出現遞歸;
(2)當預處理器搜索#define定義的符號時,字符串常量的內容並不進行檢查。
12.宏參數插入到字符常量中的兩個技巧:
(1)只有當字符串常量做爲宏參數給出時才能使用
#define PRINT(FORMAT, VALUE) \
printf("The value is "FORMAT"\n", VALUE)
PRINT("%d", x + 3);
(2)使用預處理把一個宏參數轉化爲一個字符串(#argument被預處理器翻譯爲「argument」) 將輸出:The value of x + 3 is 25
#define PRINT(FORMAT, VALUE) \
printf("The value is "FORMAT"\n", VALUE)
PRINT("%d", x + 3);
13.##結構做用是將位於它兩邊的符號鏈接成一個符號(容許宏定義從分離的文本片斷建立標識符)
#define ADD_TO_SUM(sum_number, value) \
sum ## sum_number += value
ADD_TO_SUM(5, 25);
產生的結果是將25的加到sum5中(這種鏈接必須產生一個合法的標識符,不然結果是未定義的)。
14.宏常常應用於執行簡單的計算,至於爲什麼不用函數來完成一些簡單的運算,有如下兩個緣由:
(1)用於調用和從函數返回的代碼有可能比實際執行這個小型計算工做的代碼更大,所以使用宏比使用函數在程序的規模和速度方面都更勝一籌;
(2)函數的參數必須聲明爲一個特定的類型,可是宏是與類型無關的;
可是宏也有其不利之處:使用宏會增長程序的長度,而且使用具備反作用的參數可能在宏的使用過程當中產生不可預料的結果。
15.宏和函數的不一樣之處:
16.條件編譯能夠用於調試程序和在編譯時選擇不一樣的代碼部分。
17.編譯器支持兩種不一樣類型的文件包含:函數庫文件和本地文件:
#include <filename>
#include "filename"
前者編譯器是在編譯器定義的「一系列標準位置」查找函數庫頭文件,後者編譯器是源文件所在的當前目錄下進行查找。
18.可使用條件編譯的方法來解決多重包含的問題
#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1
#endif
用上述的方法就能夠很好的消除多重包含的危險。
第十五章 輸入/輸出函數
0. stdio.h:Refer File
typedef struct {
short level; /* fill/empty level of buffer */
unsigned flags; /* File status flags */
char fd; /* File descriptor */
unsigned char hold; /* Ungetc char if no buffer */
short bsize; /* Buffer size */
unsigned char *buffer; /* Data transfer buffer */
unsigned char *curp; /* Current active pointer */
unsigned istemp; /* Temporary file indicator */
short token; /* Used for validity checking */
} FILE;
#define feof(f) ((f)->flags & _F_EOF)
1. void perror(char const *message); // 報告錯誤
2. void exit(int status); // 停止執行,status返回給操做系統
3. Fully Buffered:徹底緩衝,讀取和寫入的動做在一塊被稱爲緩衝區(buffer)的內存block進行,當buffer 寫滿的時候纔會執行刷新操做(flush)。 這就致使咱們寫入buffer的數據,並不會立馬寫入(顯示或存儲);但一次性把寫滿的buffer寫入比逐片把程序的輸出寫入效率要高。同理,buffer爲空時經過從設備或文件讀取一塊較大的輸入,從新填充buffer。在使用printf函數進行debug的時候,最好在調用printf以後強制刷新(fflush)緩衝區。
printf ("something is wrong");
fflush (stdout);
4. 流分爲兩種:Text stream 和 Binary stream。
5. FILE數據結構用於訪問流,每一個流都應有相應的FILE與其關聯。
Three stream:standard input(stdin),standard output(stdout),standard error(stderr); 它們都是指向FILE結構實例的指針。
6. EOF:提示到達文件尾,它的實際值比一個字符要多幾位,這是爲了不二進制值被錯誤的解釋爲EOF(EOF不在0~255以內),使用feof(FILE *stream)宏判斷是否到了文件尾。
7. 文件流I/O操做步驟:
A. 聲明一個FILE指針;
B. 調用fopen函數打開流,返回FILE結構的指針(爲了打開一個流,必須指定須要訪問的文件或設備,以及訪問方式,如讀、寫、讀寫…);
C. 根據須要對流進行操做;
D. 調用fclose函數關閉流;
8. 標準流的I/O不須要打開或關閉,即所謂的I/O函數,可直接使用。(stdin,stdout)
9. FILE *fopen(char const *name, char const *mode); //失敗返回NULL,應該始終檢查fopen函數返回的指針是否爲NULL。
FILE *freopen(char const *name, char const *mode, FILE *stream); // 函數首先試圖關閉這個流,而後用指定的文件和模式從新打開這個流;失敗返回NULL,成功返回他的第三個參數值。
10. int fclose(FILE *f); // 對於輸入流,fclose函數在文件關閉以前刷新buffer;若是執行成功,fclose返回0,不然返回EOF。
11. 字符I/O:
int fgetc(FILE *stream); //不存在字符,返回EOF,是函數。
int getc(FILE *stream); //不存在字符,返回EOF,是宏。
int getchar(void); // stdin:標準輸入流。
int fputc(int character, FILE *stream); //是函數。
int putc(int character, FILE *stream); //是宏。
int putchar(int character ); // stdout:標準輸出流。
int ungetc(int character, File *stream); // 把一個先前讀入的字符返回到流中。
若是一個流容許退回多個字符,那麼這些字符再次被讀取的順序是退回時的反序。
12. 未格式化的行I/O:
char *fgets(char *buffer, int buffer_size, FILE *stream); //到達文件尾部返回NULL,不然返回他的第一個參數,這個返回值一般用來檢查是否到了文件尾。
char *gets(char *buffer);
int fputs(char const *buffer, FILE *stream); //寫入錯誤返回EOF,不然返回一個非負值
int puts(char const *buffer);
fgets沒法將字符串讀入到一個長度小於兩個字符的緩衝區,由於其中一個字符須要爲NUL字節保留。
Buffer的長度由常量MAX_LINE_LENGTH決定,也就是讀取一行文本的最大長度。
13. 格式化的行I/O:
int fscanf( FILE *stream, char const *format, … ); // scan form file
int scanf( char const *format, … );
int sscanf( char const *string, char const *format, … ); // scan from string
上述格式化輸入:返回值爲被轉換的輸入值數目。
int fprintf( FILE *stream, char const *format, … );
int printf( char const *format, … );
int sprintf( char *buffer, char const *format, … );
上述格式化輸出:返回值是實際打印或存儲的字符數。
14. 二進制I/O:二進制避免了在數值轉換爲字符串的過程當中所涉及的開銷和精度損失。
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);
buffer爲緩衝區,被解釋爲一個數組,size是緩衝區每一個字符的字節數,count指定傳輸緩衝區中多少值,返回值是實際讀取或寫入的元素數目。
15. int fflush(FILE *stream); // 迫使一個輸出流緩衝區內的數據進行物理寫入,不管它滿或沒滿。
long ftell(FILE *stream); // 返回流的當前位置,ftell的值總能夠用於fseek
int fseek(FILE *stream, long offset, int from); // 用於在流中定位,from:SEEK_SET, SEEK_CUR, SEEK_END,若是form是SEEK_SET,offset必須是同一個流中之前調用ftell返回的值。
void rewind(FILE *stream); // 將讀/寫指針設置回指定流的的起始位置,同時清除流的錯誤提示標誌
int fgetpos(FILE *stream, fpos_t *position); // 功能相似ftell
int fsetpos(FILE *stream, fpos_t const *postion); // 功能相似fseek
16. 改變緩衝方式:
void setbuf(FILE *stream, char *buf); // 設置另外一個數組對流進行緩衝,長度必須爲BUFSIZ(stdio.h); 若是參數爲NULL,將關閉流的全部緩衝方式。
int setvbuf(FILE *stream, char *buf, int mode, size_t size); // mode:指定緩衝的類型,IOFBF指定一個徹底緩衝的流,IONBF指定一個不緩衝的流,IOLBF指定一個行緩衝流(每當有換行符寫入到緩衝區,緩衝區進行刷新);size爲buffer的長度。
若是須要一個很大的緩衝區,它的長度應該是BUFSIZ的整數倍。
17. 流錯誤函數:
int feof(FILE *stream); // 處於尾部返回爲真,這個狀態可經過fseek、rewind、fsetpos清除。
int ferror(FILE *stream); // 報告流的錯誤狀態,如出現任何讀/寫錯誤返回真
void clearerr(FILE *stream); // 清除流的錯誤標誌
18. Temp File:
FILE *tmpfile(void); // 文件被關閉或程序終止時臨時文件被自動刪除;建立的文件以wb+模式打開,可用於二進制和文本數據。
char *tmpnam(char *name); // 建立臨時文件的名字,調用次數不超過TMP_MAX時,每次都產生一個新的不一樣名字。參數爲NULL是,返回一個指向靜態數組的指針,數組包含了被建立的文件名。
19. 文件操做函數:執行成功返回0,失敗返回非零值
int remove(char const *filename); // 文件被打開時調用remove,其結果取決於編譯器。
int rename(char const *oldname, char const *newname);
20. 流錯誤函數,臨時文件函數tmpfile,文件操縱函數。
21. 良好的編程實踐要求任何可能產生錯誤的操做,都應該在執行以後進行檢查,肯定它是否成功。
22. 注意只有當庫函數失敗時,errno才能被設置,當函數成功運行時,errno的值不會被修改。
23. exit 函數中的參數和main中的參數狀態是一致的,用於提示程序是否正常完成,這個函數沒有返回值,當exit結束時,程序已經消失,因此他無返回值而言。
24. 標準i/o函數庫還引用了緩存i/o的概念,提升了絕大多數程序的效率。
25. 這個函數庫存在兩個缺點:1。它在某種特定的類型的機器上實現的,並無對其餘不一樣特性的機器多做考慮。2.設計這發現上述問題後,試圖去修正,可是隻要他們這麼做了這個函數庫就不標準了,程序的可移植性就會下降。
26. 使用標準 輸入輸出時,這種緩存坑引發混淆,只有當他們與交互設備並沒有聯繫時,纔會進行徹底緩存。
27. 事實上,若是程序失敗,緩存奴輸出可能不被寫入,這就可能使得關於程序出現錯誤的位置不正確,這個的解決方法是在用於調適的printf後面加上fflush, fflush迫使緩存區的內容當即寫入,無論他當即已滿。
28. 標準錯誤就是錯誤信息寫入的地方。
29. 打開流和關閉流,對關閉流是否進行檢驗的標準是:問兩個問題,操做成功應該執行什麼 ,操做失敗應該執行什麼;若是答案同樣的話,能夠不進行檢驗不然進行檢驗。
30. fget fput是真正的函數,可是getc putc getchar putchar 都是定義的宏。
31. 二進制數據避免了在數值轉換爲字符串過程當中所涉及到的開銷和精度損失,可是這些機巧只能將數據被另一個數據順序讀取時才能使用。
32. fflush迫使一個輸出流的緩存區內的數據進行物理寫入,無論他是否已滿。
33. 隨機訪問是經過讀取和寫入先前定位到文件中須要的位置來實現。
第十六章 標準庫函數
1. stdlib.h:
int abs( int value );
long int labs( long int value );
div_t div( int numerator, int denominator );
ldiv_t ldiv( long int numer, long int denom );
int rand( void );
void srand( unsigned int seed );
void srand( (unsigned int) time(0) ); // 天天的時間作爲隨機數產生器的種子
int atoi( char const *string );
long int atol( char const *string);
long int strtol( char const *string, char **unused, int base ); // base爲將要用的進制
unsigned long int strtoul( char const *string, char **unused, int base );
2. math.h:
double exp(double x); // e值的x次冪
double log(double x); // 返回以e爲底x的對數
double log10(double x);
double frexp( double value, int *exponent ); // 計算一個指數(exponent)和小數(fraction)
double ldexp( double fraction, int exponent );
double modf( double value, double *ipart ); // 把一個浮點值分紅整數和小數兩個部分
double pow(double x, double y); // 返回x的y次方
double sqrt(double x); // 返回x的平方根
double floor(double x); // 返回不大於參數的最大整數
double ceil(double x); // 返回不小於參數的最小整數
double fabs(double x); // 返回參數的絕對值
double fmod(double x, double y); // 返回x/y所產生的餘數,商必須爲整數
3. clock_t clock(void); // 返回處理器時鐘滴答的次數,除以CLOCKS_PER_SEC轉換爲秒數
time_t time(time_t *returned_value); // 參數爲NULL時返回當前時間,不爲NULL參數存儲當前時間
char *ctime(time_t const *time_value); // 格式化爲字符串
double difftime(time_t t1, time_t t2); // 返回t1-t2的時間差,並轉換爲秒
sturct tm *gmtime(time_t const *time_value); // convert to Greenwich Mean time
struct tm *localtime(time_t const *time_value); // convert to Local time
time_t mktime(struct tm *tm_ptr); // convert to time_t structure
char *asctime(sturct tm *tm); // covert to string format
sturct tm成員:tm_sec,tm_min,tm_hour,tm_mday,tm_mon,tm_year,tm_wday,tm_yday,tm_isdat。
strftime函數把一個tm結構轉換爲一個根據某個格式字符串而定的字符串。
4. setjmp的第一次調用確立一個執行點,若是調用longjmp,程序的執行流會在該地點恢復執行。(不能返回到一個已經再也不處於活動狀態的函數)
jmp_buf restart;
value = setjmp(restart);
longjmp(restart, 1);
5. 信號處理函數:signal handler,用於信號發生時程序調用這個函數進行處理。異步信號的處理函數中調用exit或abort函數是不安全的。
6. 終止執行:stdlib.h
void abort( void ); // 不正常的終止一個正在執行的程序
void atexit( void (func)( void )); // 把一個函數註冊爲退出函數;atexit函數中不要再調用exit函數,可能會致使無限循環。
void exit( status ); // exit函數被調用時,全部被atexit註冊的退出函數將按照註冊順序被反序依次調用。
7. 斷言:assert.h
void assert( int expression ); //判斷表達式的真假。爲假:向標準錯誤打印一條診斷信息並終止程序。
程序在測試完成以後,能夠在編譯事經過定義NDEBUG消除全部的斷言;使用-DNDEBUG編譯器命令行選項,或這在源文件的頭文件中assert.h被包含以前增長下面的定義:#define NDEBUG,當NDEBUG被定義以後,預處理器將丟棄全部的斷言。
8. 環境:stdlib.h
char *getenv( char const *name ); //獲取環境變量
9. 執行系統命令:stdlib.h
void system( char const *command ); // system可使用NULL參數,用於詢問命令處理器是否實際存在。
10. 排序與查找:stdlib.h
void qsort(void *base, size_t n_elements, size_t el_size, int (*fcmp)(const void *, const void * ));
void *bsearch(const void *key, const void *base, size_t nelem, size_t width, int (*fcmp)( const void *, const void * ));
11. Locale.h:一組特定的參數,每一個國家可能各不相同。
char *setlocale( int category, char const *locale );
整型函數庫:算術<stdlib.h>取絕對值,除法運算(對整型的運算包含商和餘數,返回一個結構),隨機數的<stdlib.h>其中有個小技巧:使用每一天的時間做爲隨機數產生的種子 ;字符串轉換<stdlib.h>將字符串轉換爲數值。
浮點型函數庫,<math.h>包含了剩餘數學函數的聲明,這些函數的絕大多數返回值都是double型,注意區別定義域錯誤和範圍錯誤;包含三角函數,雙曲函數,對數和指數函數,浮點形式,冪函數,底數,頂數,絕對值和餘數<math.h>轉換爲double型的字符串轉換函數,(書上標記的是在<stdlib.h>中,本人認爲是筆誤應該在<math.h>中)。
日期和時間函數:<time.h>處理器時間,當天時間其中有一個difftime函數用來計算兩個時間的差值。
第十七章 經典抽象數據類型
1. 內存存儲方案:靜態數組,動態分配的數組,動態分配的鏈式結構。
2. 堆棧:後進先出(LIFO)
先檢測,後壓棧;壓棧:top標誌先加1,後賦值。
先檢測,後出棧;出棧:先清值,top標誌後減1。(top標誌減1便可,沒必要刪除元素)
3. 隊列:先進先出(FIFO),rear進front出。
循環數組(circular array):數組的頭尾相連,可用來作隊列的儲存,組成環形隊列。
環形隊列爲空時:front – rear = 1,有一個元素時,front=rear。
從新定義隊列滿:數組中的一個元素始終保留不用,這個元素始終在隊列空間未使用部分的rear和front之間;則當(rear+1)%QUEUR_SIZE == front爲真時,隊列爲空;當(rear+2)%QUEUR_SIZE == front爲真時,隊列爲滿;
4. 二叉搜索樹(binary search tree):每一個節點最多具備兩個孩子,節點值大於左孩子,小於右孩子。
前序遍歷(pre-order):節點→左子樹→右子樹
中序遍歷(in-order):左子樹→節點→右子樹
後序遍歷(post-order):左子樹→右子樹→節點
層次遍歷(breadth-first):頂層→第二層(左右)→......→最底層(左右)
5. 數組表示二叉搜索樹
規則:根節點從1開始
A. 節點的雙親節點是N/2
B. 節點N的左孩子是2N
C. 節點N的右孩子是2N+1
規則:根節點從0開始:
A. 節點的雙親節點是(N+1)/2 - 1
B. 節點N的左孩子是2N+1
C. 節點N的右孩子是2N+2
鏈式二叉樹比數組更適合作樹的存儲。
6. #define能夠近似的模擬泛型機制。泛型是OOP處理的比較完美的問題之一。
7. 使用斷言檢查內存是否分配成功是危險的。
第十八章 運行時環境
1. 虛擬內存:由操做系統實現,在須要時把程序的活動部分放入內存並把不活動的部分複製到硬盤中,這樣就能夠容許系統運行大型的程序。
2. 是連接器而不是編譯器決定外部標識符的最大長度
3. 不能連接由不一樣編譯器產生的程序。
4. 優化程序:優化算法比優化代碼更有效果。
終於整理完了,又花了一下午時間……
這裏還有一篇
參考:
①yanghaoran321:http://my.csdn.net/yanghaoran321
②arkhe:http://www.cnblogs.com/arkhe/articles/2615965.html
③zhghost:http://blog.csdn.net/zhghost/article/details/5287865