版權(quán)聲明:本文為
gfson原創(chuàng)文章,轉(zhuǎn)載請注明出處。
注:作者水平有限,文中如有不恰當(dāng)之處,請予以指正,萬分感謝。
第2章 C 語言基本概念
2.1. 字符串字面量(String Literal)
字符串字面量是用一對雙引號(hào)包圍的一系列字符。
2.2. 把換行符加入到字符串中
- 錯(cuò)誤方式
printf("hello
World\n");
- 正確方式
printf("hello "
"World");
根據(jù) C 語言標(biāo)準(zhǔn),當(dāng)兩條或者更多條字符串字面量相連時(shí)(僅用空白字符分割),編譯器必須把它們合并成單獨(dú)一條字符串。這條規(guī)則允許把字符串分割放在兩行或更多行中。
2.3. 編譯器是完全移走注釋還是用空格替換掉注釋?
根據(jù)標(biāo)準(zhǔn) C,編譯器必須用一個(gè)空格符替換每條注釋語句。
a/**/b = 0 相當(dāng)于 a b = 0。
2.4. 在注釋中嵌套一個(gè)新的注釋是否合法?
在標(biāo)準(zhǔn) C 中是不合法的,如果需要注釋掉一段包含注釋的代碼,可以使用如下方法:
#if 0
printf("hello World\n");
#endif
這種方式經(jīng)常稱為「條件屏蔽」。
2.5. C 程序中使用 // 作為注釋的開頭,如下所示,是否合法?
// This is a comment.
在標(biāo)準(zhǔn) C 中是不合法的,// 注釋是 C++ 的方式,有一些 C 編譯器也支持,但是也有一部分編譯器不支持,為了保持程序的可移植性,因此應(yīng)該盡量避免使用 //。
第3章 格式化的輸入/輸出
3.1. 轉(zhuǎn)換說明(Conversion specification)
轉(zhuǎn)換說明以 % 開頭,轉(zhuǎn)化說明用來表示填充位置的占位符。
3.2. printf 中轉(zhuǎn)換說明的格式
轉(zhuǎn)化說明的通用格式為:
%m.pX 或者 %-m.pX
對于這個(gè)格式的解釋如下:
- m 和 p 都是整形常量,而 X 是字母。
- m 和 p 都是可選項(xiàng)。
- 如果省略 m,小數(shù)點(diǎn)要保留,例如
%.2f。 - 如果省略 p,小數(shù)點(diǎn)也要一起省略,例如
%10f。
- 如果省略 m,小數(shù)點(diǎn)要保留,例如
- m 表示的是最小字段寬度(minimum field width),指定了要顯示的最小字符數(shù)量。
- 如果要打印的數(shù)值比 m 個(gè)字符少,那么值在字段內(nèi)是右對齊的(換句話說,在數(shù)值前面放置額外的空格)。
- 如果要打印的數(shù)值比 m 個(gè)字符多,那么字段寬度會(huì)自動(dòng)擴(kuò)展為需要的尺寸,而不會(huì)丟失數(shù)字。
- 在 m 前放上一個(gè)負(fù)號(hào),會(huì)發(fā)生左對齊。
- 舉例如下:
printf("%4d\n",1); printf("%4d\n",11); printf("%4d\n",111); printf("%4d\n",1111); printf("---------------\n"); printf("%4d\n",12345); printf("---------------\n"); printf("%-4d\n",1); printf("%-4d\n",11); printf("%-4d\n",111); printf("%-4d\n",1111);

- p 表示的是精度(precision),p 的含義依賴于轉(zhuǎn)換說明符(conversion specifier) X 的值,X 表明需要對數(shù)值進(jìn)行哪種轉(zhuǎn)換。常見的轉(zhuǎn)換說明符有:
- d —— 表示十進(jìn)制的整數(shù)。當(dāng) x 為 d 時(shí),p 表示可以顯示的數(shù)字的最少個(gè)數(shù)(如果需要,就在數(shù)前加上額外的零),如果忽略掉 p,則默認(rèn)它的值為 1。
- e —— 表示指數(shù)(科學(xué)計(jì)數(shù)法)形式的浮點(diǎn)數(shù)。當(dāng) x 為 e 時(shí),p 表示小數(shù)點(diǎn)后應(yīng)該出現(xiàn)的數(shù)字的個(gè)數(shù)(默認(rèn)為 6),如果 p 為 0,則不顯示小數(shù)點(diǎn)。
- f —— 表示「定點(diǎn)十進(jìn)制」形式的浮點(diǎn)數(shù),沒有指數(shù)。p 的含義與在說明符 e 時(shí)一樣。
- g —— 將 double 值轉(zhuǎn)化為 f 形式或者 e 形式,形式的選擇根據(jù)數(shù)的大小決定。僅當(dāng)數(shù)值的指數(shù)部分小于
-4,或者指數(shù)部分大于或等于精度時(shí),會(huì)選擇 e 形式顯示。當(dāng) x 為 g 時(shí),p 表示可以顯示的有效數(shù)字的最大數(shù)量(默認(rèn)為 6)。與 f 不同,g 的轉(zhuǎn)換將不顯示尾隨零。 - 舉例如下:
printf("%.3d\n",1); printf("%.5d\n",1); printf("%.6d\n",1); printf("--------------------\n"); printf("%e\n",12.1); printf("%.2e\n",12.1); printf("%.0e\n",12.1); printf("--------------------\n"); printf("%f\n",12.1); printf("%.2f\n",12.1); printf("%.0f\n",12.1); printf("--------------------\n"); printf("%g\n",12.120000); printf("%g\n",12.12345678); printf("%.2g\n",12.1); printf("%.0g\n",12.1);

3.3. %i 和 %d 有什么區(qū)別?
在 printf 中,兩者沒有區(qū)別。在 scanf 中,%d 只能和十進(jìn)制匹配,而 %i 可以匹配八進(jìn)制、十進(jìn)制或者十六進(jìn)制。如果用戶意外將 0 放在數(shù)字的開頭處,那么用 %i 代替 %d 可能有意外的結(jié)果。由于這是一個(gè)陷阱,所以堅(jiān)持使用 %d。
3.4. printf 如何顯示字符 %?
printf 格式串中,兩個(gè)連續(xù)的 % 將顯示一個(gè) %,如下所示:
printf("%%");
第4章 表達(dá)式
4.1. 運(yùn)算符 / 和 % 注意的問題
- 運(yùn)算符
/通過丟掉分?jǐn)?shù)部分的方法截取結(jié)果。因此,1/2的結(jié)果是 0 而不是 0.5。 - 運(yùn)算符
%要求整數(shù)操作數(shù),如果兩個(gè)操作數(shù)中有一個(gè)不是整數(shù),無法編譯通過。 - 當(dāng)運(yùn)算符
/和%用于負(fù)的操作數(shù)時(shí),其結(jié)果與實(shí)現(xiàn)有關(guān)。-
-9/7的結(jié)果既可以是 -1 也可以是 -2。 -
-9%7的結(jié)果既可以是 2 也可以是 -2。
-
4.2. 由實(shí)現(xiàn)定義(implementation-defined)
- 由實(shí)現(xiàn)定義是一個(gè)術(shù)語,出現(xiàn)頻率很高。
- C 語言故意漏掉了語言未定義部分,并認(rèn)為這部分會(huì)由「實(shí)現(xiàn)」來具體定義。
- 所謂實(shí)現(xiàn),是指軟件在特定平臺(tái)上編譯、鏈接和執(zhí)行。
- C 語言為了達(dá)到高效率,需要與硬件行為匹配。當(dāng) -9 除以 7 時(shí),一些機(jī)器產(chǎn)生的結(jié)果可能是 -1,而另一些機(jī)器的結(jié)果可能是 -2。C 標(biāo)準(zhǔn)簡單的反映了這一現(xiàn)實(shí)。
- 最好避免編寫與實(shí)現(xiàn)行為相關(guān)的程序。
4.3. 賦值運(yùn)算符「=」
- 許多語言中,賦值是語句,但是 C 語言中,賦值是運(yùn)算符,換句話說,賦值操作產(chǎn)生結(jié)果。
- 賦值表達(dá)式
v = e產(chǎn)生的結(jié)果就是賦值運(yùn)算后 v 的值。 - 運(yùn)算符
=是右結(jié)合的,i = j = k = 0相當(dāng)與(i = (j = (k = 0)))。 - 由于結(jié)果發(fā)生了類型轉(zhuǎn)換,串聯(lián)賦值運(yùn)算的最終結(jié)果不是期望的結(jié)果,如下所示:
int i;
float f;
f= i =33.6;
printf("i=%d,f=%f",i,f);

4.4. 左值
- 左值(lvalue)表示儲(chǔ)存在計(jì)算機(jī)內(nèi)存中的對象,而不是常量或計(jì)算結(jié)果。
- 變量是左值,諸如 10 或者 2*i 這樣的表達(dá)式不是左值。
- 賦值運(yùn)算符要求它左邊的操作數(shù)必須是左值,以下表達(dá)式是不合法的,編譯不通過:
- 12 = i;
- i + j = 0;
- -i = j;
4.5. 子表達(dá)式的求值順序
C 語言沒有定義子表達(dá)式的求值順序(除了含有邏輯與運(yùn)算符及邏輯或運(yùn)算符、條件運(yùn)算符以及逗號(hào)運(yùn)算符的子表達(dá)式)。因此,在表達(dá)式 (a + b) * (c - d) 中,無法確定子表達(dá)式 (a + b) 是否在子表達(dá)式 (c - d) 之前求值。
- 這樣的規(guī)定隱含著陷阱,如下所示:
a = 5; c = (b = a + 2) - (a = 1);- 如果先計(jì)算
b = a + 2,則 b = 7,c = 6。 - 如果先計(jì)算
a = 1,則 b = 3,c = 2。
- 如果先計(jì)算
為了避免此問題,最好不要編寫依賴子表達(dá)式計(jì)算順序的程序,一個(gè)好的建議是:不在字表達(dá)式中使用賦值運(yùn)算符,如下所示:
a = 5;
b = a + 2;
a = 1;
c = b - a;
4.6. v += e 一定等價(jià)與 v = v + e 么?
不一定,如果 v 有副作用,則兩者不想等。
- 計(jì)算
v += e只是求一次 v 的值,而計(jì)算v = v + e需要求兩次 v 的值。任何副作用都能導(dǎo)致兩次求 v 的值不同。如下所示:a [i++] += 2; // i 自增一次 a [i++] = a [i++] + 2; // i 自增兩次
4.7. ++ 和 -- 是否可以處理 float 型變量?
可以,自增和自減可以用于所有數(shù)值類型,但是很少使用它們處理 float 類型變量。如下所示:
float f = 1.3;
printf("%f",++f);

4.8. 表達(dá)式的副作用(side effect)
表達(dá)式有兩種功能,每個(gè)表達(dá)式都產(chǎn)生一個(gè)值(value),同時(shí)可能包含副作用(side effect)。副作用是指改變了某些變量的值。如下所示:
20 // 這個(gè)表達(dá)式的值是 20,它沒有副作用,因?yàn)樗鼪]有改變?nèi)魏巫兞康闹怠? x=5 // 這個(gè)表達(dá)式的值是 5,它有一個(gè)副作用,因?yàn)樗淖兞俗兞?x 的值。
x=y++ // 這個(gè)表達(dá)示有兩個(gè)副作用,因?yàn)楦淖兞藘蓚€(gè)變量的值。
x=x++ // 這個(gè)表達(dá)式也有兩個(gè)副作用,因?yàn)樽兞?x 的值發(fā)生了兩次改變。
4.9. 順序點(diǎn)(sequence point)
表達(dá)式求值規(guī)則的核心在于順序點(diǎn)。
- 順序點(diǎn)的意思是在一系列步驟中的一個(gè)「結(jié)算」的點(diǎn),C 語言要求這一時(shí)刻的求值和副作用全部完成,才能進(jìn)入下面的部分。
- C 標(biāo)準(zhǔn)規(guī)定代碼執(zhí)行過程中的某些時(shí)刻是 Sequence Point,當(dāng)?shù)竭_(dá)一個(gè) Sequence Point 時(shí),在此之前的 Side Effect 必須全部作用完畢,在此之后的 Side Effect 必須一個(gè)都沒發(fā)。至于兩個(gè) Sequence Point 之間的多個(gè) Side Effect 哪個(gè)先發(fā)生哪個(gè)后發(fā)生則沒有規(guī)定,編譯器可以任意選擇各 Side Effect 的作用順序。
- C 語言中常見順序點(diǎn)的位置有:
- 分號(hào)
; - 未重載的逗號(hào)運(yùn)算符的左操作數(shù)賦值之后,即
;處。 - 未重載的
||運(yùn)算符的左操作數(shù)賦值之后,即||處。 - 未重載的
&&運(yùn)算符的左操作數(shù)賦值之后,即&&處。 - 三元運(yùn)算符
? :的左操作數(shù)賦值之后,即?處。 - 在函數(shù)所有參數(shù)賦值之后但在函數(shù)第一條語句執(zhí)行之前。
- 在函數(shù)返回值已拷貝給調(diào)用者之后但在該函數(shù)之外的代碼執(zhí)行之前。
- 在每一個(gè)完整的變量聲明處有一個(gè)順序點(diǎn),例如
int i, j;中逗號(hào)和分號(hào)處分別有一個(gè)順序點(diǎn)。 - for 循環(huán)控制條件中的兩個(gè)分號(hào)處各有一個(gè)順序點(diǎn)。
- 分號(hào)
第5章 選擇語句
5.1. 表達(dá)式 i < j < k 是否合法?
此表達(dá)式是合法的,相當(dāng)于 (i < j) < k,首先比較 i 是否小于 k,然后用比較產(chǎn)生的結(jié)果 1 或 0 來和 k 比較。
5.2. 如果 i 是 int 型,f 是 float 型,則條件表達(dá)式 i > 0 ? i : f 是哪一種類型的值?
當(dāng) int 和 float 混合在一個(gè)表達(dá)式中時(shí),表達(dá)式類型為 float 類型。如果 i > 0 為真,那么變量 i 轉(zhuǎn)換為 float 型后的值就是表達(dá)式的值。
第7章 基本類型
7.1. 讀 / 寫整數(shù)
- 讀寫無符號(hào)數(shù)時(shí),使用 u、o、x 代替 d。
- u:表示十進(jìn)制。
- o : 表示八進(jìn)制。
- x:表示十六進(jìn)制。
- 讀寫短整型時(shí),在 d、u、o、x 前面加上 h。
- 讀寫長整型時(shí),在 d、u、o、x 前面加上 l。
7.2. 轉(zhuǎn)義字符(numeric escape)
在 C 語言中有三種轉(zhuǎn)義字符,它們是:一般轉(zhuǎn)義字符、八進(jìn)制轉(zhuǎn)義字符和十六進(jìn)制轉(zhuǎn)義字符。
- 一般轉(zhuǎn)義字符:這種轉(zhuǎn)義字符,雖然在形式上由兩個(gè)字符組成,但只代表一個(gè)字符。常用的有:
-
\a\n\t\v\b\r\f\\\’\"
-
- 八進(jìn)制轉(zhuǎn)義字符:
- 它是由反斜杠
\和隨后的 1~3 個(gè)八進(jìn)制數(shù)字構(gòu)成的字符序列。例如,\60、\101、\141分別表示字符0、A和a。因?yàn)樽址?0、A和a的 ASCII 碼的八進(jìn)制值分別為 60、101 和 141。字符集中的所有字符都可以用八進(jìn)制轉(zhuǎn)義字符表示。如果你愿意,可以在八進(jìn)制數(shù)字前面加上一個(gè) 0 來表示八進(jìn)制轉(zhuǎn)移字符。
- 它是由反斜杠
- 十六進(jìn)制轉(zhuǎn)義字符:
- 它是由反斜杠
/和字母 x(或 X)及隨后的 1~2 個(gè)十六進(jìn)制數(shù)字構(gòu)成的字符序列。例如,\x30、\x41、\X61分別表示字符0、A和a。因?yàn)樽址?0、A和a的 ASCII 碼的十六進(jìn)制值分別為
0x30、0x41 和 0x61??梢?,字符集中的所有字符都可以用十六進(jìn)制轉(zhuǎn)義字符表示。
- 它是由反斜杠
- 由上可知,使用八進(jìn)制轉(zhuǎn)義字符和十六進(jìn)制轉(zhuǎn)義字符,不僅可以表示控制字符,而且也可以表示可顯示字符。但由于不同的計(jì)算機(jī)系統(tǒng)上采用的字符集可能不同,因此,為了能使所編寫的程序可以方便地移植到其他的計(jì)算機(jī)系統(tǒng)上運(yùn)行,程序中應(yīng)少用這種形式的轉(zhuǎn)義字符。
7.3. 讀字符的兩種慣用法
while (getchar() != '\n') /* skips rest of line */
;
while ((ch = getchar()) == ' ') /* skips blanks */
;
7.4. sizeof 運(yùn)算符
sizeof 運(yùn)算符返回的是無符號(hào)整數(shù),所以最安全的辦法是把其結(jié)果轉(zhuǎn)化為 unsigned long 類型,然后用 %lu 顯示。
printf("Size of int: %lu\n", (unsigned long)sizeof(int));
7.5. 為什么使用 %lf 讀取 double 的值,而用 %f 進(jìn)行顯示?
- 一方面,函數(shù) scanf 和 printf 有可變長度的參數(shù)列表,當(dāng)調(diào)用帶有可變長度參數(shù)列表的函數(shù)時(shí),編譯器會(huì)安排 float 自動(dòng)轉(zhuǎn)換為 double,其結(jié)果是 printf 無法分辨 float 和 double。所以在 printf 中 %f 既可以表示 float 又可以表示 double。
- 另一方面,scanf 是通過指針指向變量的。%f 告訴 scanf 函數(shù)在所傳地址上存儲(chǔ)一個(gè) float 類型的值,而 %lf 告訴 scanf 函數(shù)在所傳地址上存儲(chǔ)一個(gè) double 類型的值。這里兩者的區(qū)別很重要,如果給出了錯(cuò)誤的轉(zhuǎn)換,那么 scanf 可能存儲(chǔ)錯(cuò)誤的字節(jié)數(shù)量。
第11章 指針
11.1 指針總是和地址一樣么?
通常是,但不總是。在一些計(jì)算機(jī)上,指針可能是偏移量,而不完全是地址。
char near *p; /*定義一個(gè)字符型“近”指針*/
char far *p; /*定義一個(gè)字符型“遠(yuǎn)”指針*/
char huge *p; /*定義一個(gè)字符型“巨”指針*/
近指針、遠(yuǎn)指針、巨指針是段尋址的 16bit 處理器的產(chǎn)物(如果處理器是 16 位的,但是不采用段尋址的話,也不存在近指針、遠(yuǎn)指針、巨指針的概念),當(dāng)前普通 PC 所使用的 32bit 處理器(80386 以上)一般運(yùn)行在保護(hù)模式下的,指針都是 32 位的,可平滑地址,已經(jīng)不分遠(yuǎn)、近指針了。但是在嵌入式系統(tǒng)領(lǐng)域下,8086 的處理器仍然有比較廣泛的市場,如 AMD 公司的 AM186ED、AM186ER 等處理器,開發(fā)這些系統(tǒng)的程序時(shí),我們還是有必要弄清楚指針的尋址范圍。
- 近指針
- 近指針是只能訪問本段、只包含本段偏移的、位寬為16位的指針。
- 遠(yuǎn)指針
- 遠(yuǎn)指針是能訪問非本段、包含段偏移和段地址的、位寬為32位的指針。
- 巨指針
- 和遠(yuǎn)指針一樣,巨指針也是 32 位的指針,指針也表示為 16 位段:16 位偏移,也可以尋址任何地址。它和遠(yuǎn)指針的區(qū)別在于進(jìn)行了規(guī)格化處理。遠(yuǎn)指針沒有規(guī)格化,可能存在兩個(gè)遠(yuǎn)指針實(shí)際指向同一個(gè)物理地址,但是它們的段地址和偏移地址不一樣,如 23B0:0004 和 23A1:00F4 都指向同一個(gè)物理地址 23B04!巨指針通過特定的例程保證:每次操作完成后其偏移量均小于 10h,即只有最低 4 位有數(shù)值,其余數(shù)值都被進(jìn)位到段地址上去了,這樣就可以避免 Far 指針在 64K 邊界時(shí)出乎意料的回繞的行為。
11.2. const int * p、int * const p、const int * const p
- const int * p
- 保護(hù) p 指向的對象。
- int * const p
- 保護(hù) p 本身。
- const int * const p
- 同時(shí)保護(hù) p 和它指向的對象。
第12章 指針和數(shù)組
12.1. * 運(yùn)算符和 ++ 運(yùn)算符的組合
| 表達(dá)式 | 含義 |
|---|---|
| *p++ 或 *(p++) | 自增前表達(dá)式的值是 *p,然后自增 p |
| (*p)++ | 自增前表達(dá)式的值是 *p,然后自增 *p |
| *++p 或 *(++p) | 先自增 p,自增后表達(dá)式的值是 *p |
| ++*p 或 ++(*p) | 先自增 *p,自增后表達(dá)式的值是 *p |
12.2. i[a] 和 a[i] 是一樣的?
是的。
對于編譯器而言,i[a] 等同與 *(i+a),a[i] 等同與 *(a+i),所以兩者相同。
12.3. *a 和 a[]
- 在變量聲明中,指針和數(shù)組是截然不同的兩種類型。
- 在形式參數(shù)的聲明中,兩者是一樣的,在實(shí)踐中,*a 比 a[] 更通用,建議使用 *a。
第13章 字符串
13.1. 字符串字面量的賦值
char *p;
p = "abc";
這個(gè)賦值操作不是復(fù)制 "abc" 中的字符,而是使 p 指向字符串的第一個(gè)字符。
13.2. 如何存儲(chǔ)字符串字面量
- 從本質(zhì)上講,C 語言將字符串字面量作為字符數(shù)組來處理,為長度為 n 的字符串字面量分配 n+1 的內(nèi)存空間,最后一個(gè)空間用來存儲(chǔ)空字符
\0。 - 既然字符串字面量作為數(shù)組來儲(chǔ)存,那么編譯器會(huì)將他看作 char* 類型的指針。
13.3. 對指針添加下標(biāo)
char ch
ch = "abc"[1];
ch 的新值將是 b。
如下,將 0 - 15 的數(shù)轉(zhuǎn)化成等價(jià)的十六進(jìn)制:
char digit_to_hex_char (int digit)
{
return "0123456789ABCDEF"[digit];
}
13.4. 允許改變字符串字面量中的字符
char *p = "abc";
*p = 'b'; /* string literal is now "bbc" */
不推薦這么做,這么做的結(jié)果是未定義的,對于一些編譯器可能會(huì)導(dǎo)致程序異常。
- 針對 "abc" 來說,會(huì)在 stack 分配 sizeof(char *) 字節(jié)的空間給指針 p,然后將 p 的值修改為 "abc" 的地址,而這段地址一般位于只讀數(shù)據(jù)段中。
- 在現(xiàn)代操作系統(tǒng)中,可以將一段內(nèi)存空間設(shè)置為讀寫數(shù)據(jù)、只讀數(shù)據(jù)等等多種屬性,一般編譯器會(huì)將 "abc" 字面量放到像 ".rodata" 這樣的只讀數(shù)據(jù)段中,修改只讀段會(huì)觸發(fā) CPU 的保護(hù)機(jī)制 (#GP) 從而導(dǎo)致操作系統(tǒng)將程序干掉。
13.5. 字符數(shù)組和字符指針
char ch[] = "hello world";
char *ch = "hello world";
兩者區(qū)別如下:
- 在聲明為數(shù)組時(shí),就像任意元素一樣,可以修改存儲(chǔ)在 ch 中的字符。在聲明為指針時(shí),ch 指向字符串字面量,而修改字符串字面量會(huì)導(dǎo)致程序異常。
- 在聲明為數(shù)組時(shí),ch 是數(shù)組名。在聲明為指針時(shí),ch 是變量,這個(gè)變量可以在程序執(zhí)行期間指向其他字符串。
13.6. printf 和 puts 函數(shù)寫字符串
- 轉(zhuǎn)換說明 %s 允許 printf 寫字符串,printf 會(huì)逐個(gè)寫字符串的字符,直到遇到空字符串為止(如果空字符串丟失,則會(huì)越過字符串末尾繼續(xù)寫,直到在內(nèi)存某個(gè)地方找到空字符串為止)。
char p[] = "abc"; printf("p=%s\n",p); - 轉(zhuǎn)換說明
%m.ps和%-m.ps顯示字符串- m 表示在大小為 m 的域內(nèi)顯示字符串,對于超過 m 個(gè)字符的字符串,顯示完整字符串;對于少于 m 個(gè)字符的字符串,在域內(nèi)右對齊。為了強(qiáng)制左對齊,在 m 前加一個(gè)負(fù)號(hào)。
- p 代表要顯示的字符串的前 p 個(gè)字符。
-
%m.ps表示字符串的前 p 個(gè)字符在大小為 m 的域內(nèi)顯示。
- puts 的使用方式如下,str 就是需要顯示的字符串。在寫完字符串后,puts 總會(huì)添加一個(gè)額外的換行符。
puts(str);
13.7. scanf 和 gets 函數(shù)讀字符串
- 轉(zhuǎn)換說明 %s 允許 scanf 函數(shù)讀入字符串,如下所示。不需要在 str 前加運(yùn)算符 &,因?yàn)?str 是數(shù)組名,編譯器會(huì)自動(dòng)把它當(dāng)作指針來處理。
scanf("%s", str);- 調(diào)用時(shí),scanf 會(huì)跳過空白字符,然后讀入字符,并且把讀入的字符存儲(chǔ)到 str 中,直到遇到空白字符為止。scanf 始終會(huì)在字符串末尾存儲(chǔ)一個(gè)空字符
\0。 - 用 scanf 函數(shù)讀入字符串永遠(yuǎn)不會(huì)包括空白字符。因此,scanf 通常不會(huì)讀入一整行輸入。
- 調(diào)用時(shí),scanf 會(huì)跳過空白字符,然后讀入字符,并且把讀入的字符存儲(chǔ)到 str 中,直到遇到空白字符為止。scanf 始終會(huì)在字符串末尾存儲(chǔ)一個(gè)空字符
- gets 函數(shù)可以讀入一整行輸入。類似 scanf,gets 函數(shù)把讀到的字符存儲(chǔ)到數(shù)組中,然后存儲(chǔ)一個(gè)空字符。
gets(str); - 兩者區(qū)別:
- gets 函數(shù)不會(huì)在開始讀字符之前跳過空白字符(scanf 函數(shù)會(huì)跳過)。
- gets 函數(shù)會(huì)持續(xù)讀入直到找到換行符為止(scanf 會(huì)在任意空白符處停止)。
- gets 會(huì)忽略換行符,不會(huì)把它存儲(chǔ)到數(shù)組里,而是用空字符代替換行符。
- scanf 和 gets 函數(shù)都無法檢測何時(shí)填滿數(shù)據(jù),會(huì)有數(shù)組越界的可能。
- 使用 %ns 代替 %s 可以使 scanf 更安全,n 代表可以存儲(chǔ)的最大字符數(shù)量。
- 由于 gets 和 puts 比 scanf 和 printf 簡單,因此通常運(yùn)行也更快。
13.8. 自定義逐個(gè)字符讀字符串函數(shù)
- 在開始存儲(chǔ)之前,不跳過空白字符。
- 在第一個(gè)換行符處停止讀?。ú淮鎯?chǔ)換行符)。
- 忽略額外的字符。
#include <stdio.h>
#include <stdlib.h>
int read_line(char[] ,int);
int main(void)
{
char str[10];
int n = 10;
read_line(str, n);
printf("--- end ---");
return 0;
}
int read_line(char ch[], int n)
{
char tmp_str;
int i = 0;
while((tmp_str = getchar()) != '\n')
{
if(i<n)
{
ch[i++] = tmp_str;
}
}
ch[i] = '\0';
printf("message is:%s\n", ch);
return i;
}
13.9. 字符串處理函數(shù)
C 語言字符串庫 string.h 的幾個(gè)常見函數(shù):
- strcpy 函數(shù)(字符串復(fù)制)
char* strcpy (char* s1, const char* s2);- 把字符串 s2 賦值給 s1 直到(并且包括)s2 中遇到的一個(gè)空字符為止。
- 返回 s1。
- 如果 s2 長度大于 s1,那么會(huì)越過 s1 數(shù)組的邊界繼續(xù)復(fù)制,直到遇到空字符為止,會(huì)覆蓋未知內(nèi)存,結(jié)果無法預(yù)測。
- strcat 函數(shù)(字符串拼接)
char* strcat (char* s1, const char* s2);- 把字符串 s2 的內(nèi)容追加到 s1 的末尾
- 返回 s1。
- 如果 s1 長度不夠 s2 的追加,導(dǎo)致 s2 覆蓋 s1 數(shù)組末尾后面的內(nèi)存,結(jié)果是不可預(yù)測的。
- strcmp 函數(shù)(字符串比較)
int strcmp (const char* s1, const char* s2)- 比較 s1 和 s2,根據(jù) s1 是否小于、等于、大于 s2,會(huì)返回小于、等于、大于 0 的值。
- strcmp 利用字典順序進(jìn)行字符串比較,比較規(guī)則如下:
-
abc小于bcd,abc小于abd,abc小于abcd。 - 比較兩個(gè)字符串時(shí),strcmp 會(huì)查看表示字符的數(shù)字碼。以 ASCII 字符集為例:
- 所有大寫字母(65 ~ 90)都小于小寫字母(97 ~ 122)。
- 數(shù)字(48 ~ 57)小于字母。
- 空格符(32)小于所有打印字符。
-
- strlen 函數(shù)(求字符串長度)
size_t strlen (const char* s1)- 返回 s1 中第一個(gè)空字符串前的字符個(gè)數(shù),但不包括第一個(gè)空字符串。
- 當(dāng)數(shù)組作為函數(shù)實(shí)際參數(shù)時(shí),strlen 不會(huì)測量數(shù)組的長度,而是返回?cái)?shù)組中的字符串長度。
13.10. 字符串慣用法
- strlen 的簡單實(shí)現(xiàn)
size_t strlen(const char * str) {
const char *cp = str;
while (*cp++ )
;
return (cp - str - 1 );
}
- strcat 的簡單實(shí)現(xiàn)
char* strcat ( char * dst , const char * src )
{
char * cp = dst;
while( *cp )
cp++; /* find end of dst */
while( *cp++ = *src++ ) ; /* Copy src to end of dst */
return( dst ); /* return dst */
}
13.11. 存儲(chǔ)字符串?dāng)?shù)組的兩種方式
- 二維字符數(shù)組
char planets[][8] = {"Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto"};
這種方式浪費(fèi)空間,如下所示:

- 字符串指針數(shù)組
char *planets[] = {"Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto"};
推薦這種方式,如下所示:

13.12. read_line 檢測讀入字符是否失敗
- 增加對 EOF 的判斷。
int read_line(char ch[], int n)
{
char tmp_str;
int i = 0;
while((tmp_str = getchar()) != '\n' && ch != EOF)
{
if(i<n)
{
ch[i++] = tmp_str;
}
}
ch[i] = '\0';
printf("message is:%s\n", ch);
return i;
}
第14章 預(yù)處理器
14.1. # 運(yùn)算符
宏定義中使用 # 運(yùn)算符可以將宏的參數(shù)(調(diào)用宏時(shí)傳遞過來的實(shí)參)轉(zhuǎn)化為字符串字面量,如下所示:
#define PRINT_INT(x) printf(#x " = %d\n", x)
宏展開后的結(jié)果是:
printf("x" " = %d\n", x) 等價(jià)于 printf("x = %d\n", x)
14.2. ## 運(yùn)算符
宏定義中使用 ## 運(yùn)算符可以將兩個(gè)記號(hào)粘在一起,成為一個(gè)記號(hào)。如果其中有宏參數(shù),則會(huì)在形式參數(shù)被實(shí)際參數(shù)替換后進(jìn)行粘合。如下所示:
#define MK_ID(n) i##n
int MK_ID(1), MK_ID(2), MK_ID(3);
上述宏展開后結(jié)果為:
int i1, i2, i3;
14.3. 宏定義的作用范圍
一個(gè)宏定義的作用范圍通常到出現(xiàn)這個(gè)宏的文件末尾。
- 由于宏是由預(yù)處理器處理的,他們不遵從通常的范圍規(guī)則。
- 一個(gè)定義在函數(shù)內(nèi)的宏,并不僅在函數(shù)內(nèi)起作用,而是作用的文件末尾。
14.4. 宏定義加圓括號(hào)
- 如果宏的替換列表有運(yùn)算符,始終要將替換列表放在圓括號(hào)中。
- 如果宏有參數(shù),參數(shù)每次在替換列表出現(xiàn)的時(shí)候,必須放在圓括號(hào)中。
14.5. 預(yù)定義宏
| 宏名字 | 宏類型 | 宏作用 |
|---|---|---|
| __LINE__ | 當(dāng)前行的行數(shù) | 整型常量 |
| __FILE__ | 當(dāng)前源文件的名字 | 字符串字面量 |
| __DATE__ | 編譯的日期(Mmm dd yyyy) | 字符串字面量 |
| __TIME__ | 編譯的時(shí)間(hh:mm:ss) | 字符串字面量 |
| __STDC__ | 如果編譯器接收標(biāo)準(zhǔn) C,那么值為 1 | 整型常量 |
第15章 編寫大規(guī)模程序
15.1. 共享宏定義和類型定義

15.2. 共享函數(shù)原型

- 文件 stack.c 中包含 stack.h,以便編譯器檢查 stack.h 中的函數(shù)原型是否與 stack.c 中的函數(shù)定義相匹配。
- 文件 calc.c 中包含 stack.h,以便編譯器檢查每個(gè)函數(shù)的返回類型,以及形式參數(shù)的數(shù)量和類型是否匹配。
15.3. 共享變量聲明
在共享變量共享之前,不需要區(qū)分其定義和聲明。
- 為了聲明變量,寫成如下形式,這樣不僅聲明了 i,也對 i 進(jìn)行了定義,從而使編譯器為 i 留出了空間:
int i; /* declares i and defines it as well */ - 在其他文件中,想要共享這個(gè)變量的話,需要聲明沒有定義的變量 i,使用關(guān)鍵字 extern,extern 提示編譯器變量 i 是在其他程序中定義的,因此不需要為 i 分配空間。
extern int i; /* declares i without defining it */
15.4. 保護(hù)頭文件
為了防止頭文件多次包含,將用 #ifndef 和 #endif 兩個(gè)指令把文件的內(nèi)容閉合起來。例如,如下的方式保護(hù) boolean.h:
#ifndef BOOLEAN_H
#define BOOLEAN
#define TRUE 1
#define FALSE 0
typedef int Bool;
#endif
第16章 結(jié)構(gòu)、聯(lián)合和枚舉
16.1. 結(jié)構(gòu)使用 = 運(yùn)算符復(fù)制
數(shù)組不能使用 = 運(yùn)算符復(fù)制,結(jié)構(gòu)可以使用 = 運(yùn)算符復(fù)制:
struct { int a[10]; } a1, a2;
a1 = a2; /* legal, since a1 and a2 are structures */
16.2. 表示結(jié)構(gòu)的類型
C 語言提供了兩種命名結(jié)構(gòu)的方法:
- 聲明 “結(jié)構(gòu)標(biāo)記”
struct part { int number; char name[NAME_LEN+1]; int on_hand; }; /* 結(jié)構(gòu)標(biāo)記的聲明:右大括號(hào)的分號(hào)不能省略,表明聲明的結(jié)束 */ struct part part1, part2; /* 結(jié)構(gòu)變量的聲明:不能漏掉單詞 struct 來縮寫這個(gè)聲明 */ part part1, part2; /*** WRONG,part 不是類型名,沒有 struct,part 沒有任何意義 ***/ struct part { int number; char name[NAME_LEN+1]; int on_hand; } part1, part2; /* 結(jié)構(gòu)標(biāo)記的聲明可以和結(jié)構(gòu)變量的聲明合在一起 */ - 使用 typedef 來定義類型名
typedef struct { int number; char name[NAME_LEN+1]; int on_hand; } Part; /* 類型 Part 的名字必須出現(xiàn)在定義的末尾,而不是在 struct 后面 */ Part part1, part2; /* 可以像內(nèi)置類型一樣使用 Part,由于 Part 是 typedef 定義的,所以不能寫 Struct Part */
16.3. 聯(lián)合的兩個(gè)應(yīng)用
- 使用聯(lián)合來節(jié)省空間
- 使用聯(lián)合來構(gòu)造混亂的數(shù)據(jù)結(jié)構(gòu)
-
實(shí)現(xiàn)數(shù)組的元素類型是 int 和 float 的混合
typedef union { int i; float f; } Number; Number number_array[1000]; number_array[0].i = 5; number_array[1].f = 8.396;
-
實(shí)現(xiàn)數(shù)組的元素類型是 int 和 float 的混合
16.4. 為聯(lián)合添加 "標(biāo)記字段"
為了判斷聯(lián)合中成員的類型,可以把聯(lián)合嵌入一個(gè)結(jié)構(gòu)中,且此結(jié)構(gòu)還含有另一個(gè)成員 "標(biāo)記字段",用來提示當(dāng)前存儲(chǔ)在聯(lián)合中的內(nèi)容的類型,如下所示:
#define INT_KIND 0
#define FLOAT_KIND 1
typedef struct {
int kind; /* tag field */
union {
int i;
float f;
} u;
} Number;
void print_number (Number n){
if (n.kind == INT_KIND){
printf("%d", n.u.i);
} else {
printf("%g", n.u.f);
}
}
16.5. 枚舉
在一些程序中,我們可能需要變量只具有少量有意義的值,例如布爾類型應(yīng)該只有兩種可能,真值或假值。
C 語言為少量可能值的變量設(shè)計(jì)了枚舉類型。
- 定義枚舉標(biāo)記和枚舉變量。類似 struct 的定義,可以通過 "枚舉標(biāo)記" 或者 typedef 兩者方法定義枚舉類型。
enum bool { FALSE, TRUE }; /* 定義枚舉標(biāo)記 */ enum bool b1, b2; typedef enum { FALSE, TRUE } Bool; /* 使用 typedef 進(jìn)行定義類型 Bool */ Bool b1, b2; - 枚舉作為整數(shù)
在系統(tǒng)內(nèi)部,C 語言會(huì)把枚舉變量和常量作為整數(shù)來處理。-
當(dāng)沒有為枚舉常量指定值時(shí),它的值是一個(gè)大于前一個(gè)常量的值(默認(rèn)第一個(gè)枚舉常量值為 0)。
enum colors { BLACK, WHITE, GRAY = 6, YELLOW, RED = 15 } ; /* BLACK = 0, WHITE = 1,YELLOW = 7 */ - 枚舉的值和整數(shù)混用,編譯器會(huì)把 c 當(dāng)作整型處理,而
BLACK, WHITE, GRAY, YELLOW, RED只是數(shù)0,1,2,3,4的同義詞。int i; enum { BLACK, WHITE, GRAY, YELLOW, RED } c; i = WHITE; /* i is now 1 */ c = 0; /* c is now 0 (BLACK)*/ c++; /* c is now 1 (WHITE)*/ i = c + 2; /* i is now 3 */
-
當(dāng)沒有為枚舉常量指定值時(shí),它的值是一個(gè)大于前一個(gè)常量的值(默認(rèn)第一個(gè)枚舉常量值為 0)。
- 用枚舉聲明聯(lián)合的 "標(biāo)記字段",這樣做的優(yōu)勢不僅遠(yuǎn)離了宏 INT_KIND 和 FLOAT_KIND (他們現(xiàn)在是枚舉常量),而且闡明了 kind 的含義。
typedef struct { enum { INT_KIND, FLOAT_KIND } kind; union { int i; float f; } u; } Number;
第17章 指針的高級(jí)應(yīng)用
17.1. 內(nèi)存分配函數(shù)
- malloc:分配內(nèi)存塊,但是不對內(nèi)存塊進(jìn)行初始化。
- calloc:分配內(nèi)存塊,并且對內(nèi)存塊進(jìn)行清除。
- realloc:調(diào)整先前分配的內(nèi)存塊。
17.2. 空指針
當(dāng)調(diào)用內(nèi)存分配函數(shù)時(shí),無法定位滿足我們需要的足夠大的內(nèi)存塊時(shí),函數(shù)會(huì)返回空指針。
- 空指針是 "指向?yàn)榭盏闹羔?,這是區(qū)別與所有有效指針的特殊值。
- 程序員的責(zé)任是測試任意內(nèi)存分配函數(shù)的返回值,并且在返回空指針時(shí)進(jìn)行適當(dāng)?shù)牟僮鳌?/li>
對指針的處理慣用法如下:
p = malloc(1000);
if (p == NULL){
/* allocation failed; take appropriate action */
} /* 慣用法一 */
/***************************************************/
if ((p = malloc(1000)) == NULL){
/* allocation failed; take appropriate action */
} /* 慣用法二 */
/***************************************************/
p = malloc(1000);
if (!p){
/* allocation failed; take appropriate action */
} /* 慣用法三,C 語言中非空指針都為真,只有空指針為假 */
17.3. 使用 malloc 動(dòng)態(tài)分配字符串
malloc 函數(shù)原型如下:
void *malloc ( size_t size );
- malloc 分配 size 字節(jié)的內(nèi)存塊,并且返回指向此內(nèi)存塊的指針。
- 通常情況下,可以把
void*類型賦值給任何指針類型的變量。 - 為 n 個(gè)字符串分配空間,可以寫成
p = malloc(n + 1);。
返回指向 "新" 字符串的指針的函數(shù),沒有改變原來的兩個(gè)字符串:
char *concat( const char *s1, const char *s2 ){
char *result;
result = malloc(strlen(s1) + strlen(s2) + 1);
if (result == NULL){
printf("Error: malloc failed in concat\n");
exit(EXIT_FAILURE);
}
strcpy(result, s1);
strcat(result, s2);
return result;
}
17.4. 使用 malloc 為數(shù)組分配內(nèi)存空間
需要使用 sizeof 運(yùn)算符來計(jì)算每個(gè)元素所需要的空間大?。?/p>
int *a;
a = malloc( n * sizeof(int) );
一旦 a 指向動(dòng)態(tài)的內(nèi)存塊,就可以把 a 當(dāng)作數(shù)組的名字。
17.5. 使用 calloc 為數(shù)組分配內(nèi)存
calloc 函數(shù)原型如下:
void *calloc ( size_t nmemb, size_t size );
- calloc 函數(shù)為 nmemb 個(gè)元素的數(shù)組分配內(nèi)存空間,其中每個(gè)元素的長度都是 size 個(gè)字節(jié)。
- 如果要求的空間無效,那么此函數(shù)返回空指針。
- 在分配了內(nèi)存以后,calloc 會(huì)通過對所有位設(shè)為 0 的方式進(jìn)行初始化。
通過調(diào)用以 1 為第一個(gè)實(shí)際參數(shù)的 calloc 函數(shù),可以為任何類型的數(shù)據(jù)項(xiàng)分配空間。
struct point {
int x;
int y;
} *p;
p = calloc(1, sizeof(struct point)); /* p 執(zhí)行結(jié)構(gòu),且此結(jié)構(gòu)的成員 x,y 都會(huì)被設(shè)為 0 */
17.6. 使用 realloc 函數(shù)調(diào)整先前分配的內(nèi)存塊
一旦為數(shù)組分配完內(nèi)存,后面 realloc 函數(shù)可以調(diào)整數(shù)組的大小使它更適合需要。
realloc 的原型如下:
void *realloc (void *ptr, size_t size);
當(dāng)調(diào)用 realloc 時(shí),ptr 必須指向內(nèi)存塊,且此內(nèi)存塊一定是先前通過 malloc 函數(shù)、calloc 函數(shù)或 realloc 函數(shù)的調(diào)用獲得的。
size 表示內(nèi)存塊的新尺寸,可能會(huì)大于或小于原有尺寸。
-
C 標(biāo)準(zhǔn)中關(guān)于 realloc 函數(shù)的規(guī)則:
- 當(dāng)擴(kuò)展內(nèi)存塊時(shí),realloc 函數(shù)不會(huì)對添加進(jìn)內(nèi)存塊的函數(shù)進(jìn)行初始化。
- 如果 realloc 函數(shù)不能按要求擴(kuò)大內(nèi)存塊,那么它會(huì)返回空指針,并且原有內(nèi)存塊中的數(shù)據(jù)不會(huì)發(fā)生改變。
- 如果 realloc 函數(shù)調(diào)用時(shí)以空指針作為第一個(gè)實(shí)際參數(shù),那么它的行為就像 malloc 函數(shù)一樣。
- 如果 realloc 函數(shù)調(diào)用時(shí)以 0 作為第二個(gè)實(shí)際參數(shù),那么它會(huì)釋放掉內(nèi)存塊。
-
realloc 使用建議:
- 當(dāng)要求減少內(nèi)存塊大小時(shí),realloc 函數(shù)應(yīng)該在 "適當(dāng)位置" 縮減內(nèi)存塊,而不需要移動(dòng)存儲(chǔ)在內(nèi)存塊中的數(shù)據(jù)。
- 當(dāng)要求擴(kuò)大內(nèi)存塊大小時(shí),realloc 函數(shù)應(yīng)該始終試圖擴(kuò)大內(nèi)存塊而不需要對其進(jìn)行移動(dòng)。
- 如果無法擴(kuò)大內(nèi)存塊(因?yàn)閮?nèi)存塊后面的字節(jié)已經(jīng)用于其他目的),realloc 函數(shù)會(huì)在別處分配新的內(nèi)存塊,并把舊塊中的內(nèi)容復(fù)制到新塊中。
- 一旦 realloc 函數(shù)返回,一定要對指向內(nèi)存塊的所有指針更新,因?yàn)?realloc 函數(shù)可能移動(dòng)了其他地方的內(nèi)存塊。
17.7. 釋放內(nèi)存 free 函數(shù)
free 函數(shù)原型如下:
void free(void* ptr);
使用 free 函數(shù),只要把指向不再需要內(nèi)存塊的指針傳遞給 free 函數(shù)即可,如下所示:
p = malloc (...);
q = malloc (...);'
free (p);
p = q;
- free 函數(shù)的實(shí)際參數(shù)必須是指針,而且此指針一定是先前被內(nèi)存分配函數(shù)返回的。
懸空指針(dangling point)的問題:
- 調(diào)用
free(p);釋放了 p 指向的內(nèi)存塊,但是 p 本身不會(huì)改變。如果忘記了 p 不再指向有效的內(nèi)存塊,可能會(huì)出現(xiàn)問題,如下所示:char *p = malloc(4); free(p); strcpy(p, "abc"); /*** WRONG。錯(cuò)誤,修改 p 指向的內(nèi)存是嚴(yán)重錯(cuò)誤的,因?yàn)槌绦虿辉賹Υ藘?nèi)存有控制權(quán)。***/ - 懸空指針很難發(fā)現(xiàn),因?yàn)閹讉€(gè)指針可能指向相同的內(nèi)存塊。在釋放內(nèi)存塊時(shí),全部指針可能都留有懸空。
第18章 聲明
18.1. 聲明的語法
在大多數(shù)通過格式中,聲明格式如下:
[ 聲明的格式 ] 聲明說明符 聲明符;
聲明說明符(declaration specifier)描述聲明的數(shù)據(jù)項(xiàng)的性質(zhì)。聲明符(declarator)給出了數(shù)據(jù)項(xiàng)的名字,并可以提供關(guān)于數(shù)據(jù)項(xiàng)的額外信息。
- 聲明說明符分為以下三類:
- 存儲(chǔ)類型:存儲(chǔ)類型一共四種,auto、static、extern 和 register。聲明中最多出現(xiàn)一種存儲(chǔ)類型,如果出現(xiàn)存儲(chǔ)類型,則需要放在聲明的首要位置。
- 類型限定符:只有兩種類型限定符,const 和 volatile。聲明可以指定一個(gè)、兩個(gè)限定符或者一個(gè)也沒有。
- 類型說明符:關(guān)鍵字 void、char、short、int、long、float、double、signed 和 unsigned 都是
類型說明符。這些出現(xiàn)的順序不是問題(int unsigned long 和 long unsigned int 完全一樣)。類型說明符也包括結(jié)構(gòu)、聯(lián)合和枚舉的說明。用 typedef 創(chuàng)建的類型名也是類型說明符。
18.2. 變量的存儲(chǔ)類型
C 程序中每個(gè)變量都具有 3 個(gè)性質(zhì):
-
存儲(chǔ)期限
- 變量的存儲(chǔ)期限決定了為變量預(yù)留和釋放內(nèi)存的時(shí)間。
- 具有自動(dòng)存儲(chǔ)期限的變量在所屬塊被執(zhí)行時(shí)獲得內(nèi)存單元,并在塊終止時(shí)釋放內(nèi)存單元。
- 具有靜態(tài)存儲(chǔ)期限的變量在程序運(yùn)行期間占有同樣的存儲(chǔ)單元,也就是可以允許變量無限期的保留它的值。
-
作用域
- 變量的作用域是指引用變量的那部分文本。
- 變量可以有塊作用域(變量從聲明的地方一直到閉合塊的末尾都是可見的)。
- 變量可以有文件作用域(變量從聲明的地方一直到閉合文件的末尾都是可見的)。
-
鏈接
- 變量的鏈接確定了程序的不同部分可以分享此程序的范圍。
- 具有外部鏈接的變量可以被程序中幾個(gè)(或許全部)文件共享。
- 具有內(nèi)部鏈接的變量只能屬于單獨(dú)一個(gè)文件,但是此文件中的函數(shù)可以共享這個(gè)變量。
- 無鏈接的變量屬于單獨(dú)一個(gè)變量,而且根本不能被共享。
變量的默認(rèn)存儲(chǔ)期限、作用域和鏈接都依賴于變量聲明的位置:
- 在塊內(nèi)部(包括函數(shù)體)聲明的變量具有自動(dòng)存儲(chǔ)期限、塊作用域,并且無鏈接。
- 在程序的最外層,任意塊外部聲明的變量具有靜態(tài)存儲(chǔ)類型、文件作用域和外部鏈接。
- 如下圖所示:
對許多變量而言,默認(rèn)的存儲(chǔ)期限、作用域和鏈接是可以符合要求的。當(dāng)這些性質(zhì)無法滿足要求時(shí),可以通過指定明確的存儲(chǔ)類型來改變變量的特性:auto、static、extern 和 register。
-
auto 存儲(chǔ)類型
- auto 存儲(chǔ)類型只對屬于塊的變量有效。
- auto 類型是自動(dòng)存儲(chǔ)期限、塊作用域,并且無鏈接。
- 在塊內(nèi)部的變量,默認(rèn)是 auto 類型,不需要明確指定。
-
static 存儲(chǔ)類型
- static 存儲(chǔ)類型可以用于全部變量,不需要考慮變量聲明的位置。
- 塊外部聲明變量和塊內(nèi)部聲明變量的效果不同。
- 在塊外部時(shí),static 說明變量具有內(nèi)部鏈接。
- 在塊內(nèi)部時(shí),static 把變量的存儲(chǔ)期限從自動(dòng)變成了靜態(tài)。
- 如下所示:
-
extern 存儲(chǔ)類型
- extern 存儲(chǔ)類型可以使幾個(gè)源文件共享同一個(gè)變量。
- extern 聲明中的變量始終具有靜態(tài)存儲(chǔ)期限。
- 變量的作用域依賴于變量的位置。
- 變量在塊內(nèi)部,具有塊作用域。
- 變量在塊外部,具有文件作用域。
- extern 變量的鏈接不是確定的。
- 如果變量在文件中較早的位置(任何函數(shù)定義的外部)聲明為 static,那么它具有內(nèi)部鏈接。
- 否則(通常情況),變量具有外部鏈接。
- 如下所示:
-
register 存儲(chǔ)類型
- register 存儲(chǔ)類型要求編譯器把變量存儲(chǔ)在寄存器中,而不是內(nèi)存中。
- 指明 register 類型是一種要求,而不是命令。
- register 只對聲明在塊內(nèi)的變量有效,和 auto 類型一樣是自動(dòng)存儲(chǔ)期限、塊作用域,并且無鏈接。
- 跟 auto 相比,由于寄存器沒有地址,所以 register 存儲(chǔ)類型使用取地址符
&是非法的,
18.3. 函數(shù)的存儲(chǔ)類型
函數(shù)的聲明(和定義)可以包含存儲(chǔ)類型,但是選項(xiàng)只有 extern 和 static。
- 函數(shù)在默認(rèn)情況(不指明存儲(chǔ)類型)下,具有外部鏈接,允許其他文件調(diào)用此函數(shù)。
- extern 說明函數(shù)具有外部鏈接,函數(shù)默認(rèn)是 extern 類型,不需要明確使用 extern。
- static 說明函數(shù)具有內(nèi)部鏈接,只能在定義函數(shù)的文件內(nèi)調(diào)用此函數(shù)。
- 函數(shù)的形式參數(shù)具有和 auto 變量相同的性質(zhì):自動(dòng)存儲(chǔ)期限、塊作用域,和無鏈接。
- 唯一能用于說明形式參數(shù)存儲(chǔ)類型的就是 register。
四種類型中最重要的就是 extern 和 static 了,auto 沒有任何效果,而現(xiàn)代編譯器已經(jīng)使得 register 變得廢棄無用了。
18.4. 類型限定符 const
- const 用來聲明一些類似于變量的對象,但這些變量是 “只讀” 的。程序可以訪問 const 型對象的值,但無法改變它的值。
- 不同于宏,不可以把 const 型對象用于常量表達(dá)式。
const int n = 10; int a[n]; /*** WRONG ***/ - 我們使用 const 主要是為了保護(hù)存儲(chǔ)在數(shù)組中的常量數(shù)據(jù)。
18.5. 解釋復(fù)雜聲明
兩條簡單的規(guī)則可以用來理解任何的聲明:
- 始終從內(nèi)往外讀聲明符。換句話說,定位用來聲明的標(biāo)識(shí)符,并且從此處的聲明開始解釋。
- 在作選擇時(shí),始終先是
[]和()后是*。- 如果
*在標(biāo)識(shí)符前面,而在標(biāo)識(shí)符后面有[],那么標(biāo)識(shí)符表示數(shù)組而不是指針。 - 如果
*在標(biāo)識(shí)符前面,而在標(biāo)識(shí)符后面有(),那么標(biāo)識(shí)符表示函數(shù)而不是指針。
- 如果
18.6. 初始化式
- 為了方便,C 語言允許在聲明變量時(shí)為他們指定初始值。
- 為了初始化變量,可以在聲明符后面寫
=,然后在其后再跟上初始化式(不要把聲明中的符號(hào) = 和賦值運(yùn)算符混淆,初始化和賦值不一樣)。 - 控制初始化式的額外規(guī)則:
- 具有靜態(tài)存儲(chǔ)期限的變量的初始化式必須是常量。
- 如果變量具有自動(dòng)存儲(chǔ)期限,那它的初始化式不需要常量。
- 用大括號(hào)封閉的數(shù)組、結(jié)構(gòu)或聯(lián)合的初始化式只能包含常量表達(dá)式,不能有變量或函數(shù)調(diào)用。
第20章 低級(jí)程序設(shè)計(jì)
20.1. 結(jié)構(gòu)中的位域
C 語言可以聲明結(jié)構(gòu)中其成員表示位域的結(jié)構(gòu)。
- 例如,使用 16 位來存儲(chǔ)日期,其中 5 位用于日,4 位用于月,7 位用于年。
- 利用位域,可以如下定義:
struct file_data { unsigned int day : 5; unsigned int mouth : 4; unsigned int year : 7; }
控制位域存儲(chǔ)的技巧:
- 忽略位域的名字,未命名的位域通常用來作為字段間的 "填充",以保證其他位域存儲(chǔ)在適當(dāng)?shù)奈恢谩?
struct file_data { unsigned int : 5; /* not used */ unsigned int mouth : 4; unsigned int year : 7; } - 指定未命名的字段長度為 0。長度為 0 的位域是給編譯器一個(gè)信號(hào),告訴編譯器將下一個(gè)位域放在一個(gè)存儲(chǔ)單元的起始位置。
struct file_data { unsigned int a : 4; unsigned int : 0; /* 如果存儲(chǔ)單元是 8 位,編譯器會(huì)給 a 分配 4 位,跳過余下的 4 位到下一個(gè)存儲(chǔ)單元,給 b 分配 8 位。 */ unsigned int b : 8; }
20.2. volatile 類型限定符
使用 volatile 類型限定符,我們可以通知編譯器程序使用了內(nèi)存空間 "易變" 的數(shù)據(jù)(例如從鍵盤緩沖區(qū)讀取的數(shù)據(jù))。

第21章 標(biāo)準(zhǔn)庫
21.1 標(biāo)準(zhǔn)庫概述
以下是標(biāo)準(zhǔn)庫中的 15 個(gè)頭。
- <assert.h> 診斷
- 僅包含 assert 宏,可以在程序中插入該宏,從而檢測程序狀態(tài)。一旦任何檢查失敗,程序終止。
- <ctype.h> 字符處理
- 包括用于字符分類及大小寫轉(zhuǎn)換的函數(shù)。
- <errno.h> 錯(cuò)誤
- 提供了 errno(error number)。errno 是一個(gè)左值,可以在調(diào)用特定函數(shù)后進(jìn)行檢測,來判斷調(diào)用過程中是否有錯(cuò)誤發(fā)生。
- <float.h> 浮點(diǎn)型的特性
- 提供了用于描述浮點(diǎn)類型特性的宏,包括值的范圍及精度。
- <limits.h> 整形的大小
- 提供了用于描述整數(shù)類型和字符類型的宏,包括它們的最大值和最小值。
- <locale.h> 本地化
- 提供一些函數(shù)來幫助程序適應(yīng)針對一個(gè)國家或地區(qū)的特定行為方式。
- <math.h> 數(shù)學(xué)計(jì)算
- 提供了大量用于數(shù)學(xué)計(jì)算的函數(shù)。
- <setjmp.h> 非本地跳轉(zhuǎn)
- 提供了 setjmp 函數(shù)和 longjmp 函數(shù)。
- <signal.h> 信號(hào)處理
- 提供了用于異常情況(信號(hào))處理的函數(shù),包括中斷和運(yùn)行時(shí)錯(cuò)誤。
- <stdarg.h> 可變實(shí)際參數(shù)
- 提供函數(shù)可以處理不定個(gè)數(shù)個(gè)參數(shù)的工具。
- <stddef.h> 常用定義
- 提供了經(jīng)常使用的類型和宏。
- <stdio.h> 輸入/輸出
- 提供大量用于輸入輸出的函數(shù)。
- <stdlib.h> 常用使用程序
- 包含大量無法歸類于其他頭的函數(shù)。
- <string.h> 字符串處理
- 提供用于字符串操作的函數(shù)。
- <time.h> 日期和時(shí)間
- 提供相應(yīng)的函數(shù)來獲取日期和時(shí)間、操縱時(shí)間和以多種方式顯示時(shí)間。
第22章 輸入 / 輸出
22.1. 標(biāo)準(zhǔn)流

22.2. fopen 函數(shù)打開文件的模式
- 打開文本文件的模式
- 打開二進(jìn)制文件的模式
22.3. 從命令行打開文件
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
if (argc != 2)
{
printf("usage: canopen filename\n");
return 2;
}
if ((fp = fopen(argv[1], "r")) == NULL)
{
printf("%s can't be opened\n", argv[1]);
return 1;
}
printf("%s can be opened\n", argv[1]);
fclose(fp);
return 0;
}
22.4. ...printf 類函數(shù)
使用符號(hào)
*填充格式串中的常量
22.5. ...scanf 類函數(shù)

22.6. 復(fù)制文件
/* Copies a file */
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *source_fp, *dest_fp;
int ch;
if (argc != 3)
{
fprintf(stderr, "usage: fcopy source dest\n");
exit(EXIT_FAILURE);
}
if ((source_fp = fopen(argv[1], "rb")) == NULL)
{
fprintf(stderr, "Can't open %s\n", argv[1]);
exit(EXIT_FAILURE);
}
if ((dest_fp = fopen(argv[2], "wb")) == NULL)
{
fprintf(stderr, "Can't open %s\n", argv[2]);
fclose(source_fp);
exit(EXIT_FAILURE);
}
while ((ch = getc(source_fp)) != EOF)
{
putc(ch, dest_fp);
}
fclose(source_fp);
fclose(dest_fp);
return 0;
}
參考
- 標(biāo)準(zhǔn) C 參考手冊:
http://www.runoob.com/cprogramming/c-standard-library-stdio-h.html









