《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 分別表示字符 0专执、Aa淮捆。因?yàn)樽址?0Aa 的 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 小于 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;
      

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è)元素所需要的空間大小:

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)系作者
  • 序言:七十年代末淫痰,一起剝皮案震驚了整個(gè)濱河市最楷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌待错,老刑警劉巖籽孙,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異火俄,居然都是意外死亡犯建,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門烛占,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胎挎,“玉大人,你說我怎么就攤上這事忆家∮坦剑” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵芽卿,是天一觀的道長揭芍。 經(jīng)常有香客問我,道長卸例,這世上最難降的妖魔是什么称杨? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮筷转,結(jié)果婚禮上姑原,老公的妹妹穿的比我還像新娘。我一直安慰自己呜舒,他們只是感情好锭汛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般唤殴。 火紅的嫁衣襯著肌膚如雪般婆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天朵逝,我揣著相機(jī)與錄音蔚袍,去河邊找鬼。 笑死配名,一個(gè)胖子當(dāng)著我的面吹牛啤咽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播段誊,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼墙基,長吁一口氣:“原來是場噩夢啊……” “哼于毙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蹲蒲,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤涩哟,失蹤者是張志新(化名)和其女友劉穎索赏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贴彼,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潜腻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了器仗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片融涣。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖精钮,靈堂內(nèi)的尸體忽然破棺而出威鹿,到底是詐尸還是另有隱情,我是刑警寧澤轨香,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布忽你,位于F島的核電站,受9級(jí)特大地震影響臂容,放射性物質(zhì)發(fā)生泄漏科雳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一脓杉、第九天 我趴在偏房一處隱蔽的房頂上張望糟秘。 院中可真熱鬧,春花似錦球散、人聲如沸尿赚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吼畏。三九已至督赤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泻蚊,已是汗流浹背躲舌。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留性雄,地道東北人没卸。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像秒旋,于是被迫代替她去往敵國和親约计。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

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