《C 語言程序設(shè)計(jì):現(xiàn)代方法》復(fù)習(xí)查漏筆記

版權(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 表示的是最小字段寬度(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。

為了避免此問題,最好不要編寫依賴子表達(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)。

第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 分別表示字符 0Aa。因?yàn)樽址?0、Aa 的 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、Aa。因?yàn)樽址?0Aa 的 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ì)讀入一整行輸入。
  • 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 小于 bcdabc 小于 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;
      

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 */
      
  • 用枚舉聲明聯(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;
}

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 指針是C語言中廣泛使用的一種數(shù)據(jù)類型。 運(yùn)用指針編程是C語言最主要的風(fēng)格之一。利用指針變量可以表示各種數(shù)據(jù)結(jié)構(gòu); ...
    朱森閱讀 3,614評(píng)論 3 44
  • 一、框架 1、Mac系統(tǒng)及常用工具、進(jìn)制;C數(shù)據(jù)類型、常量變量、運(yùn)算符、表達(dá)式、格式化輸入輸出 2、關(guān)系運(yùn)算符、邏...
    師景福閱讀 836評(píng)論 0 2
  • 酒是一種營養(yǎng)價(jià)值很高的飲料。適量飲用可增加高密度脂蛋白含量,減少動(dòng)脈內(nèi)膽固醇,從而防止心臟病;并有促進(jìn)食欲,...
    若愛養(yǎng)生閱讀 4,239評(píng)論 0 1
  • UI 稿 功能描述 默認(rèn)選中全部,即獲取全部列表數(shù)據(jù)。當(dāng)點(diǎn)擊“通識(shí)類”或“實(shí)訓(xùn)類”按鈕時(shí),切換選中選項(xiàng),改變路由,...
    baby熊_熊姐閱讀 928評(píng)論 2 0
  • 工作之余,習(xí)字兩篇,《邊城》看了一半,簡書溜達(dá)半天,與兩個(gè)筆友相談甚歡。
    無為而字閱讀 255評(píng)論 2 5

友情鏈接更多精彩內(nèi)容