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