這本書分爲11章,比較有趣也是吸引個人主要仍是數組,指針以及聲明的那幾章節。由於我本身的背景是偏硬件的,因此對於內存等偏硬件的章節並非那麼感興趣。所以在筆記上我也會更側重前者。本篇文章是前3章的讀書筆記,我準備經過2篇文章來完成整本書的讀書筆記。segmentfault
這章主要是介紹C語言的歷史以及C語言的各類規範。在1.9節中,文中給出了一段小代碼:數組
foo(const char **p){} main(int argc, char **argv) { foo(argv) }
這段代碼在編譯過程當中會有warning,warning的大體意思就是參數與原型不匹配。爲何不匹配?由於形參是 const
,而實參卻沒有 const
。app
參數的傳遞相似於賦值語句,要使其沒有warning,必須知足這個條件:左右兩邊的操做數都是指向有/無限定符的相容類型的指針,而且左邊的操做數必須包含右邊操做數所有的限定符。做者以爲這句話不夠直觀,所以他給出了一個例子:函數
char *cp; const char *ccp; ccp = cp;
左操做數是指向沒有限定符的指向 char
類型的指針 cp
;而右操做數則是指向有 const
限定符的指向 char
類型的指針 ccp
。也就是說左右操做數都指向 char
類型的指針,只是左邊指向的 char
還有 const
這個限定符,而且這個限定符是修飾 char
的。所以知足上面這個條件,因此這麼寫是沒有warning的。指針
回到有warning的這個例子,實參是 const char **p
,形參是 char **argv
,實參指向 const char *p
,行參指向 char *argv
。由於 const char *p
和 char *argv
不相容,所以會出現這個warning。 code
以前我一直沒明白爲何爲何 const char *p
和 char *argv
不相容而 const char
和 char
確是相容的。後來仔細想了想,這應該是和 const
修飾指針有關。 咱們先來看看下面這兩種指針的區別:內存
/* p的值能夠改變,而p所指向空間的值不能改變 */ const char *p /* p的值不能改變,而p所指向空間的值能夠改變 */ char *const p
從這裏咱們能夠看出, const char *p
並非指指針的值不能修改(也就是說 const
並非修飾指針的),而是指指針所指的空間是 const
的。所以 const char *p
和 char *argv
並不相容。我有一個未驗證的猜測,若是將 const char *p
換爲 char *const p
,也許這裏就不會報錯了,由於除去限定符,他們就是徹底同樣的指針。get
1.10主要討論了有符號數和無符號數以及隱式類型轉化。對於有無符號數而言,當咱們對其進行混合操做時, 有符號數都會默認轉換爲無符號數 ,這很容易產生bug(尤爲是比較語句中),所以咱們要儘可能避免混合使用它們。原型
這一章節講了C語言一些可能引發bug的特性。it
switch
語句忘寫 break
很容易會形成fall through。雖然有時候咱們刻意不加 break
,但這麼作的時候必定要當心,不然很容易出錯。
對於C語言中的運算符,有些在不一樣上下文中會有不一樣的意義(重載),好比 *
和 &
符號。 *
既能夠表示乘號,也能夠用於對指針取值。 &
既能夠做爲位運算符,也能夠做爲取地址操做符。
除了可能引發歧義外,運算符的優先級也很容易形成bug。好比 int *ap[]
,因爲 []
的優先級要高於 *
,因此 ap
是一個元素爲 int *
的數組,而不是一個指向int類型數組的指針。書中給了一個很好地建議: 除了加減乘除外,當涉及其它運算符時一概加上括號。
除此以外,對於X(a) = Y(b) + Z(e) * H(d)這樣的表達式,咱們並不能確認各個函數哪一個先完成,哪一個後完成。也就是說Y(b),Z(e)和H(d)可能在任意時刻返回,咱們惟一肯定的就是當其都返回後,乘法先運算,加法後運算。所以,若是這些表達式有 相互依賴關係 ,咱們就不能再這樣寫了。
函數是不能返回一個指向局部變量的指針(或者數組)的。書中給了一個例子:
char *localized_time(char * filename) { char buffer[120]; /* 對這個buffer進行各類處理 */ ... return buffer; }
由於 buffer
是一個局部變量,當這個函數結束時, buffer
所指向的空間已經被系統所收回(銷燬),咱們並不能知道此時該空間存儲的內容。所以即便咱們能獲得這個空間的地址,咱們也不能獲得咱們想要獲得的數據了。要想獲得正確的返回值,書中給出了幾種解決方案,好比使用全局變量(包括 static
),好比手動分配空間等。
這一章是主要講的是如何讀懂C語言的聲明。C語言的能夠很簡單也能夠很複雜,對於簡單的聲明咱們根本不須要花時間去分析。但對於複雜的聲明,每每對於初學者來講是一場噩夢。(在《C缺陷與陷阱》這本書中,做者也花了很大的篇幅來說解C語言的聲明)
做者用一個例子來說解如何讀C語言的聲明:
char *const *(*next)()
若是以前沒有遇到過相似的聲明,你確定會以爲無從下手。做者給出了一個通常性的方法來讀懂這些複雜的聲明:
/* A 聲明從它的名字開始讀取,而後按照優先級順序依次讀取; B 優先級從高到低依次是: B.1 聲明中被括號括起來的那部分; B.2 後綴操做符: 括號()表示這是一個函數,而 放括號[]表示這是一個數組; B.3 前綴操做符:星號*表示這是一個「指向...的指針」; C 若是const和(或)volatile關鍵字的後面緊跟類型說明符(如int,long等),那麼它做用於類型說明符。在其餘狀況下,它做用於關鍵字左邊緊鄰的指針星號。 */
下面咱們就用這個方法來讀懂這個複雜的聲明:
首先,名字是 next
,而且其被括號括起來。
而後咱們看括號外的那部分,其前綴是 *
,後綴是 ()
。由於 ()
優先級高於 *
,所以能夠判斷 next
是一個函數指針,其指向一個返回...的函數。
看完後綴咱們再看前綴,前綴是 *
,所以能夠知道這個函數是返回一個...類型的指針。
再看前面的 char *const
,咱們知道該函數返回的指針類型是指向 char
的常量指針。
除了這個例子,書中還給出了另一個例子:
char *(*c[10])(int **p)
咱們再來看看怎麼讀懂這個聲明:
名字是 c
。
它是一個數組。
數組的元素是函數指針。
這個函數的參數是 int **p
。
這個函數的返回類型的 char *
。
所以,這個語句聲明瞭一個數組,數組中的元素是指向返回值爲char指針,參數爲 int **p
的函數指針。
相對於這兩個例子而言,《C缺陷與陷阱》中的那個例子更復雜,若是想了解的話能夠翻閱個人另一篇文章C缺陷與陷阱讀書筆記。
對於複雜的聲明,使用 typedef
每每是一個很好的方。
書中給了一個例子:
void (*signal(int sig, void(* func)(int)))(int);
signal
是一個函數,這個函數返回一個 void (* )(int)
類型的函數指針。它的參數,一個是 int
類型,另外一個是 void(* )(int)
類型的函數指針。直接分析這個聲明是須要花一番功夫的,但若是咱們使用 typoof
,這個聲明就會很容易理解了:
typedef void (* p_func)(int); p_func signal(int sig, p_func);
typedef
和 define
均可以用於定義數據類型,但它們有兩個很大的區別,第一, define
後的數據類型能夠用其餘數據類型進行擴展,但 typedef
就不行;第二, typedef
能保證在連續變量的聲明中,全部變量類型保持一致,而 define
不能。
/* 第一個區別 */ #define apple int typedef int orange; /* 這個沒問題 */ unsigned apple i; /* 這個會報錯 */ unsigned orange j; /* 第二個區別 */ #define apple int * typedef int * orange; /* int * i, j - i是指針而j是int */ apple i, j; /* x和y都是指針 */ orange x, y;