《C專家編程》讀書筆記(1-3章)

這本書分爲11章,比較有趣也是吸引個人主要仍是數組,指針以及聲明的那幾章節。由於我本身的背景是偏硬件的,因此對於內存等偏硬件的章節並非那麼感興趣。所以在筆記上我也會更側重前者。本篇文章是前3章的讀書筆記,我準備經過2篇文章來完成整本書的讀書筆記。segmentfault

第一章:C穿越時空的迷霧

這章主要是介紹C語言的歷史以及C語言的各類規範。在1.9節中,文中給出了一段小代碼:數組

foo(const char **p){}

main(int argc, char **argv)
{
    foo(argv)
}

這段代碼在編譯過程當中會有warning,warning的大體意思就是參數與原型不匹配。爲何不匹配?由於形參是 const ,而實參卻沒有 constapp

參數的傳遞相似於賦值語句,要使其沒有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 *pchar *argv 不相容,所以會出現這個warning。 code

以前我一直沒明白爲何爲何 const char *pchar *argv 不相容而 const charchar 確是相容的。後來仔細想了想,這應該是和 const 修飾指針有關。 咱們先來看看下面這兩種指針的區別:內存

/* p的值能夠改變,而p所指向空間的值不能改變 */
const char *p
/* p的值不能改變,而p所指向空間的值能夠改變 */
char *const p

從這裏咱們能夠看出, const char *p 並非指指針的值不能修改(也就是說 const 並非修飾指針的),而是指指針所指的空間是 const 的。所以 const char *pchar *argv 並不相容。我有一個未驗證的猜測,若是將 const char *p 換爲 char *const p ,也許這裏就不會報錯了,由於除去限定符,他們就是徹底同樣的指針。get

1.10主要討論了有符號數和無符號數以及隱式類型轉化。對於有無符號數而言,當咱們對其進行混合操做時, 有符號數都會默認轉換爲無符號數 ,這很容易產生bug(尤爲是比較語句中),所以咱們要儘可能避免混合使用它們。原型

第二章:這不是bug,而是語言特性

這一章節講了C語言一些可能引發bug的特性。it

  1. switch 語句忘寫 break 很容易會形成fall through。雖然有時候咱們刻意不加 break ,但這麼作的時候必定要當心,不然很容易出錯。

  2. 對於C語言中的運算符,有些在不一樣上下文中會有不一樣的意義(重載),好比 *& 符號。 * 既能夠表示乘號,也能夠用於對指針取值。 & 既能夠做爲位運算符,也能夠做爲取地址操做符。

  3. 除了可能引發歧義外,運算符的優先級也很容易形成bug。好比 int *ap[] ,因爲 [] 的優先級要高於 * ,因此 ap 是一個元素爲 int * 的數組,而不是一個指向int類型數組的指針。書中給了一個很好地建議: 除了加減乘除外,當涉及其它運算符時一概加上括號。
    除此以外,對於X(a) = Y(b) + Z(e) * H(d)這樣的表達式,咱們並不能確認各個函數哪一個先完成,哪一個後完成。也就是說Y(b),Z(e)和H(d)可能在任意時刻返回,咱們惟一肯定的就是當其都返回後,乘法先運算,加法後運算。所以,若是這些表達式有 相互依賴關係 ,咱們就不能再這樣寫了。

  4. 函數是不能返回一個指向局部變量的指針(或者數組)的。書中給了一個例子:

char *localized_time(char * filename)
{
    char buffer[120];
    /* 對這個buffer進行各類處理 */
    ...
    return buffer;
}

由於 buffer 是一個局部變量,當這個函數結束時, buffer 所指向的空間已經被系統所收回(銷燬),咱們並不能知道此時該空間存儲的內容。所以即便咱們能獲得這個空間的地址,咱們也不能獲得咱們想要獲得的數據了。要想獲得正確的返回值,書中給出了幾種解決方案,好比使用全局變量(包括 static ),好比手動分配空間等。

第三章:分析C語言的聲明

這一章是主要講的是如何讀懂C語言的聲明。C語言的能夠很簡單也能夠很複雜,對於簡單的聲明咱們根本不須要花時間去分析。但對於複雜的聲明,每每對於初學者來講是一場噩夢。(在《C缺陷與陷阱》這本書中,做者也花了很大的篇幅來說解C語言的聲明)

做者用一個例子來說解如何讀C語言的聲明:

char *const *(*next)()

若是以前沒有遇到過相似的聲明,你確定會以爲無從下手。做者給出了一個通常性的方法來讀懂這些複雜的聲明:

/*
A 聲明從它的名字開始讀取,而後按照優先級順序依次讀取;
B 優先級從高到低依次是:
    B.1 聲明中被括號括起來的那部分;
    B.2 後綴操做符:
        括號()表示這是一個函數,而
        放括號[]表示這是一個數組;
    B.3 前綴操做符:星號*表示這是一個「指向...的指針」;
C 若是const和(或)volatile關鍵字的後面緊跟類型說明符(如int,long等),那麼它做用於類型說明符。在其餘狀況下,它做用於關鍵字左邊緊鄰的指針星號。
*/

下面咱們就用這個方法來讀懂這個複雜的聲明:

  1. 首先,名字是 next ,而且其被括號括起來。

  2. 而後咱們看括號外的那部分,其前綴是 * ,後綴是 () 。由於 () 優先級高於 * ,所以能夠判斷 next 是一個函數指針,其指向一個返回...的函數。

  3. 看完後綴咱們再看前綴,前綴是 * ,所以能夠知道這個函數是返回一個...類型的指針。

  4. 再看前面的 char *const ,咱們知道該函數返回的指針類型是指向 char 的常量指針。

除了這個例子,書中還給出了另一個例子:

char *(*c[10])(int **p)

咱們再來看看怎麼讀懂這個聲明:

  1. 名字是 c

  2. 它是一個數組。

  3. 數組的元素是函數指針。

  4. 這個函數的參數是 int **p

  5. 這個函數的返回類型的 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);

typedefdefine 均可以用於定義數據類型,但它們有兩個很大的區別,第一, 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;
相關文章
相關標籤/搜索