這本書(shū)分為11章,比較有趣也是吸引我的主要還是數(shù)組,指針以及聲明的那幾章節(jié)驴娃。因?yàn)槲易约旱谋尘笆瞧布耐詫?duì)于內(nèi)存等偏硬件的章節(jié)并不是那么感興趣。因此在筆記上我也會(huì)更側(cè)重前者吹榴。本篇文章是前3章的讀書(shū)筆記,我準(zhǔn)備通過(guò)2篇文章來(lái)完成整本書(shū)的讀書(shū)筆記。
第一章:C穿越時(shí)空的迷霧
這章主要是介紹C語(yǔ)言的歷史以及C語(yǔ)言的各種規(guī)范脸哀。在1.9節(jié)中,文中給出了一段小代碼:
foo(const char **p){}
main(int argc, char **argv)
{
foo(argv)
}
這段代碼在編譯過(guò)程中會(huì)有warning扭吁,warning的大致意思就是參數(shù)與原型不匹配撞蜂。為什么不匹配?因?yàn)樾螀⑹?const
侥袜,而實(shí)參卻沒(méi)有 const
蝌诡。
參數(shù)的傳遞類(lèi)似于賦值語(yǔ)句,要使其沒(méi)有warning枫吧,必須滿(mǎn)足這個(gè)條件:左右兩邊的操作數(shù)都是指向有/無(wú)限定符的相容類(lèi)型的指針浦旱,并且左邊的操作數(shù)必須包含右邊操作數(shù)全部的限定符。作者覺(jué)得這句話不夠直觀九杂,因此他給出了一個(gè)例子:
char *cp;
const char *ccp;
ccp = cp;
左操作數(shù)是指向沒(méi)有限定符的指向 char
類(lèi)型的指針 cp
颁湖;而右操作數(shù)則是指向有 const
限定符的指向 char
類(lèi)型的指針 ccp
宣蠕。也就是說(shuō)左右操作數(shù)都指向 char
類(lèi)型的指針,只是左邊指向的 char
還有 const
這個(gè)限定符甥捺,并且這個(gè)限定符是修飾 char
的抢蚀。因此滿(mǎn)足上面這個(gè)條件,所以這么寫(xiě)是沒(méi)有warning的涎永。
回到有warning的這個(gè)例子思币,實(shí)參是 const char **p
,形參是 char **argv
羡微,實(shí)參指向 const char *p
谷饿,行參指向 char *argv
。因?yàn)?const char *p
和 char *argv
不相容妈倔,因此會(huì)出現(xiàn)這個(gè)warning博投。
之前我一直沒(méi)明白為什么為什么 const char *p
和 char *argv
不相容而 const char
和 char
確是相容的。后來(lái)仔細(xì)想了想盯蝴,這應(yīng)該是和 const
修飾指針有關(guān)毅哗。 我們先來(lái)看看下面這兩種指針的區(qū)別:
/* p的值可以改變,而p所指向空間的值不能改變 */
const char *p
/* p的值不能改變捧挺,而p所指向空間的值可以改變 */
char *const p
從這里我們可以看出虑绵,const char *p
并不是指指針的值不能修改(也就是說(shuō) const
并不是修飾指針的),而是指指針?biāo)傅目臻g是 const
的闽烙。因此 const char *p
和 char *argv
并不相容翅睛。我有一個(gè)未驗(yàn)證的猜想,如果將 const char *p
換為 char *const p
黑竞,也許這里就不會(huì)報(bào)錯(cuò)了捕发,因?yàn)槌ハ薅ǚ麄兙褪峭耆粯拥闹羔槨?/p>
1.10主要討論了有符號(hào)數(shù)和無(wú)符號(hào)數(shù)以及隱式類(lèi)型轉(zhuǎn)化很魂。對(duì)于有無(wú)符號(hào)數(shù)而言扎酷,當(dāng)我們對(duì)其進(jìn)行混合操作時(shí),有符號(hào)數(shù)都會(huì)默認(rèn)轉(zhuǎn)換為無(wú)符號(hào)數(shù)遏匆,這很容易產(chǎn)生bug(尤其是比較語(yǔ)句中)法挨,因此我們要盡量避免混合使用它們。
第二章:這不是bug幅聘,而是語(yǔ)言特性
這一章節(jié)講了C語(yǔ)言一些可能引起bug的特性凡纳。
switch
語(yǔ)句忘寫(xiě)break
很容易會(huì)造成fall through。雖然有時(shí)候我們刻意不加break
喊暖,但這么做的時(shí)候一定要小心惫企,否則很容易出錯(cuò)撕瞧。對(duì)于C語(yǔ)言中的運(yùn)算符陵叽,有些在不同上下文中會(huì)有不同的意義(重載)狞尔,比如
\*
和&
符號(hào)。*
既可以表示乘號(hào)巩掺,也可以用于對(duì)指針取值偏序。&
既可以作為位運(yùn)算符,也可以作為取地址操作符胖替。除了可能引起歧義外研儒,運(yùn)算符的優(yōu)先級(jí)也很容易造成bug。比如
int *ap[]
独令,由于[]
的優(yōu)先級(jí)要高于*
端朵,所以ap
是一個(gè)元素為int *
的數(shù)組,而不是一個(gè)指向int類(lèi)型數(shù)組的指針燃箭。書(shū)中給了一個(gè)很好地建議:除了加減乘除外冲呢,當(dāng)涉及其它運(yùn)算符時(shí)一律加上括號(hào)。
除此之外招狸,對(duì)于X(a) = Y(b) + Z(e) * H(d)這樣的表達(dá)式敬拓,我們并不能確認(rèn)各個(gè)函數(shù)哪個(gè)先完成,哪個(gè)后完成裙戏。也就是說(shuō)Y(b)乘凸,Z(e)和H(d)可能在任意時(shí)刻返回,我們唯一確定的就是當(dāng)其都返回后累榜,乘法先運(yùn)算营勤,加法后運(yùn)算。因此信柿,如果這些表達(dá)式有相互依賴(lài)關(guān)系冀偶,我們就不能再這樣寫(xiě)了。函數(shù)是不能返回一個(gè)指向局部變量的指針(或者數(shù)組)的渔嚷。書(shū)中給了一個(gè)例子:
char *localized_time(char * filename)
{
char buffer[120];
/* 對(duì)這個(gè)buffer進(jìn)行各種處理 */
...
return buffer;
}
因?yàn)?buffer
是一個(gè)局部變量进鸠,當(dāng)這個(gè)函數(shù)結(jié)束時(shí),buffer
所指向的空間已經(jīng)被系統(tǒng)所收回(銷(xiāo)毀)形病,我們并不能知道此時(shí)該空間存儲(chǔ)的內(nèi)容客年。因此即使我們能得到這個(gè)空間的地址,我們也不能得到我們想要得到的數(shù)據(jù)了漠吻。要想得到正確的返回值量瓜,書(shū)中給出了幾種解決方案,比如使用全局變量(包括 static
)途乃,比如手動(dòng)分配空間等绍傲。
第三章:分析C語(yǔ)言的聲明
這一章是主要講的是如何讀懂C語(yǔ)言的聲明。C語(yǔ)言的可以很簡(jiǎn)單也可以很復(fù)雜,對(duì)于簡(jiǎn)單的聲明我們根本不需要花時(shí)間去分析烫饼。但對(duì)于復(fù)雜的聲明猎塞,往往對(duì)于初學(xué)者來(lái)說(shuō)是一場(chǎng)噩夢(mèng)。(在《C缺陷與陷阱》這本書(shū)中杠纵,作者也花了很大的篇幅來(lái)講解C語(yǔ)言的聲明)
作者用一個(gè)例子來(lái)講解如何讀C語(yǔ)言的聲明:
char *const *(*next)()
如果之前沒(méi)有遇到過(guò)類(lèi)似的聲明荠耽,你肯定會(huì)覺(jué)得無(wú)從下手。作者給出了一個(gè)一般性的方法來(lái)讀懂這些復(fù)雜的聲明:
/*
A 聲明從它的名字開(kāi)始讀取比藻,然后按照優(yōu)先級(jí)順序依次讀嚷亮俊;
B 優(yōu)先級(jí)從高到低依次是:
B.1 聲明中被括號(hào)括起來(lái)的那部分银亲;
B.2 后綴操作符:
括號(hào)()表示這是一個(gè)函數(shù)慢叨,而
放括號(hào)[]表示這是一個(gè)數(shù)組;
B.3 前綴操作符:星號(hào)*表示這是一個(gè)“指向...的指針”务蝠;
C 如果const和(或)volatile關(guān)鍵字的后面緊跟類(lèi)型說(shuō)明符(如int插爹,long等),那么它作用于類(lèi)型說(shuō)明符请梢。在其他情況下赠尾,它作用于關(guān)鍵字左邊緊鄰的指針星號(hào)。
*/
下面我們就用這個(gè)方法來(lái)讀懂這個(gè)復(fù)雜的聲明:
- 首先毅弧,名字是
next
气嫁,并且其被括號(hào)括起來(lái)。 - 然后我們看括號(hào)外的那部分够坐,其前綴是
*
寸宵,后綴是()
。因?yàn)?()
優(yōu)先級(jí)高于*
元咙,因此可以判斷next
是一個(gè)函數(shù)指針梯影,其指向一個(gè)返回...的函數(shù)。 - 看完后綴我們?cè)倏辞熬Y庶香,前綴是
*
甲棍,因此可以知道這個(gè)函數(shù)是返回一個(gè)...類(lèi)型的指針。 - 再看前面的
char *const
赶掖,我們知道該函數(shù)返回的指針類(lèi)型是指向char
的常量指針感猛。
除了這個(gè)例子,書(shū)中還給出了另外一個(gè)例子:
char *(*c[10])(int **p)
我們?cè)賮?lái)看看怎么讀懂這個(gè)聲明:
- 名字是
c
奢赂。 - 它是一個(gè)數(shù)組陪白。
- 數(shù)組的元素是函數(shù)指針。
- 這個(gè)函數(shù)的參數(shù)是
int **p
膳灶。 - 這個(gè)函數(shù)的返回類(lèi)型的
char *
咱士。
因此,這個(gè)語(yǔ)句聲明了一個(gè)數(shù)組,數(shù)組中的元素是指向返回值為char指針序厉,參數(shù)為 int **p
的函數(shù)指針拆吆。
相對(duì)于這兩個(gè)例子而言,《C缺陷與陷阱》中的那個(gè)例子更復(fù)雜脂矫,如果想了解的話可以翻閱我的另外一篇文章C缺陷與陷阱讀書(shū)筆記。
對(duì)于復(fù)雜的聲明霉晕,使用 typeof
往往是一個(gè)很好的方庭再。
書(shū)中給了一個(gè)例子:
void (*signal(int sig, void(* func)(int)))(int);
signal
是一個(gè)函數(shù),這個(gè)函數(shù)返回一個(gè) void (* )(int)
類(lèi)型的函數(shù)指針牺堰。它的參數(shù)拄轻,一個(gè)是 int
類(lèi)型,另一個(gè)是 void(* )(int)
類(lèi)型的函數(shù)指針伟葫。直接分析這個(gè)聲明是需要花一番功夫的恨搓,但如果我們使用 typoof
,這個(gè)聲明就會(huì)很容易理解了:
typeof void (* p_func)(int);
p_func signal(int sig, p_func);
typeof
和 define
都可以用于定義數(shù)據(jù)類(lèi)型筏养,但它們有兩個(gè)很大的區(qū)別斧抱,第一,define
后的數(shù)據(jù)類(lèi)型可以用其他數(shù)據(jù)類(lèi)型進(jìn)行擴(kuò)展渐溶,但 typedef
就不行辉浦;第二,typedef
能保證在連續(xù)變量的聲明中茎辐,所有變量類(lèi)型保持一致宪郊,而 define
不能。
/* 第一個(gè)區(qū)別 */
#define apple int
typedef int orange;
/* 這個(gè)沒(méi)問(wèn)題 */
unsigned apple i;
/* 這個(gè)會(huì)報(bào)錯(cuò) */
unsigned orange j;
/* 第二個(gè)區(qū)別 */
#define apple int *
typedef int * orange;
/* int * i, j - i是指針而j是int */
apple i, j;
/* x和y都是指針 */
orange x, y;