• c語言讀書筆記

    時間:2022-06-24 12:50:52 讀書筆記 我要投稿
    • 相關推薦

    c語言讀書筆記

    C語言是一門通用計算機編程語言,應用廣泛。C語言的設計目標是提供一種能以簡易的方式編譯、處理低級存儲器、產生少量的機器碼以及不需要任何運行環境支持便能運行的編程語言。品才網整理了C語言的讀書筆記,歡迎大家閱讀。

    c語言讀書筆記

    c語言讀書筆記

    《C 語言深度解剖》這本書是一本“解開程序員面試筆試的秘密”的好書。作者陳正沖老師提出“以含金量勇敢挑戰國內外同類書籍”,確實,這本書中的知識點都是一些在面試中常見的考點,并且很多都是我們平常不注意的點,對于我們深入理解C語言確實很有幫助。

    第1章關鍵字

    1.register

    雖然寄存器的速度非?,但是使用register修飾符也有些限制的:register變量必須是能被CPU寄存器所接受的類型。

    意味著register變量必須是一個單個的值,并且其長度應小于或等于整型的長度。而且register變量可能不存放在內存中,

    所以不能用取址運算符“&”來獲取register變量的地址。

    2.static修飾符

    (1)修飾變量

    靜態局部變量,在函數體里面定義的,就只能在這個函數里用了,同一個文檔中的其他函數也用不了。由于被static修飾的變量總是存在內存的靜態區,所以即使這個函數運行結束,這個靜態變量的值還是不會被銷毀,函數下次使用時仍然能用到這個值。

    (2)修飾函數

    第二個作用:修飾函數。函數前加static使得函數成為靜態函數。但此處“static”的含義不是指存儲方式,而是指對函數的作用域僅局限于本文件(所以又稱內部函數)。使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名。

    關鍵字static有著不尋常的歷史。起初,在C中引入關鍵字static是為了表示退出一個塊后仍然存在的局部變量。隨后,static在C中有了第二種含義:用來表示不能被其它文件訪問的全局變量和函數。為了避免引入新的關鍵字,所以仍使用static關鍵字來表示這第二種含義。

    3.if語句使用注意

    先處理正常情況,再處理異常情況。

    在編寫代碼是,要使得正常情況的執行代碼清晰,確認那些不常發生的異常情況處理代碼不會遮掩正常的執行路徑。這樣對于代碼的可讀性和性能都很重要。因為,if

    語句總是需要做判斷,而正常情況一般比異常情況發生的概率更大(否則就應該把異常正常調過來了),如果把執行概率更大的代碼放到后面,也就意味著if語句將進行多次無謂的比較。

    另外,非常重要的一點是,把正常情況的處理放在if后面,而不要放在else后面。當然這也符合把正常情況的處理放在前面的要求。

    4.千萬小心又小心使用void指針類型。

    按照ANSI(AmericanNationalStandardsInstitute)標準,不能對void指針進行算法操作,即下列操作都是不合法的:

    void*pvoid;

    pvoid++;//ANSI:錯誤

    pvoid+=1;//ANSI:錯誤

    ANSI標準之所以這樣認定,是因為它堅持:進行算法操作的指針必須是確定知道其指向數據類型大小的。也就是說必須知道內存目的地址的確切值。

    例如:

    int*pint;

    pint++;//ANSI:正確

    但是大名鼎鼎的GNU(GNU'sNotUnix的遞歸縮寫)則不這么認定,它指定void*的算法操作與char*一致。因此下列語句在GNU編譯器中皆正確:

    pvoid++;//GNU:正確

    pvoid+=1;//GNU:正確

    在實際的程序設計中,為符合ANSI標準,并提高程序的可移植性,我們可以這樣編寫實現同樣功能的代碼:

    void*pvoid;

    (char*)pvoid++;//ANSI:正確;GNU:正確

    (char*)pvoid+=1;//ANSI:錯誤;GNU:正確

    GNU和ANSI還有一些區別,總體而言,GNU較ANSI更“開放”,提供了對更多語法的支持。但是我們在真實設計時,還是應該盡可能地符合ANSI標準。

    5.const與宏

    節省空間,避免不必要的內存分配,同時提高效率

    編譯器通常不為普通const只讀變量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的值,沒有了存儲與讀內存的操作,使得它的效率也很高。

    例如:

    #define M 3//宏常量

    const int N=5;//此時并未將N放入內存中

    ......

    inti=N;//此時為N分配內存,以后不再分配!

    intI=M;//預編譯期間進行宏替換,分配內存

    intj=N;//沒有內存分配

    intJ=M;//再進行宏替換,又一次分配內存!

    const定義的只讀變量從匯編的角度來看,只是給出了對應的內存地址,而不是象#define一樣給出的是立即數,所以,const定義的只讀變量在程序運行過程中只有一份拷貝(因為它是全局的只讀變量,存放在靜態區),而#define定義的宏常量在內存中有若干個拷貝。#define宏是在預編譯階段進行替換,而const修飾的只讀變量是在編譯的時候確定其值。

    #define宏沒有類型,而const修飾的只讀變量具有特定的類型。

    6.最易變的關鍵字----volatile

    volatile是易變的、不穩定的意思。很多人根本就沒見過這個關鍵字,不知道它的存在。也有很多程序員知道它的存在,但從來沒用過它。我對它有種“楊家有女初長成,養在深閨人未識”的感覺。volatile關鍵字和const一樣是一種類型修飾符,用它修飾的變量表示可以被某些編譯器未知的因素更改,比如操作系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。

    先看看下面的例子:

    int i=10;

    int j=i;//(1)語句

    int k=i;//(2)語句

    這時候編譯器對代碼進行優化,因為在(1)(2)兩條語句中,i沒有被用作左值。這時候編譯器認為i的值沒有發生改變,所以在(1)語句時從內存中取出i的值賦給j

    之后,這個值并沒有被丟掉,而是在(2)語句時繼續用這個值給k賦值。編譯器不會生成出匯編代碼重新從內存里取i的值,這樣提高了效率。但要注意:(1)(2)語句之間i沒有被用作左值才行。

    再看另一個例子:

    volatile int i=10;

    int j=i;//(3)語句

    int k=i;//(4)語句

    volatile關鍵字告訴編譯器i是隨時可能發生變化的,每次使用它的時候必須從內存中取出i的值,因而編譯器生成的匯編代碼會重新從i的地址處讀取數據放在k中。

    這樣看來,如果i是一個寄存器變量或者表示一個端口數據或者是多個線程的共享數據,就容易出錯,所以說volatile可以保證對特殊地址的穩定訪問。

    但是注意:在VC++6.0中,一般Debug模式沒有進行代碼優化,所以這個關鍵字的作用有可能看不出來。你可以同時生成Debug版和Release版的程序做個測試。

    留一個問題:const volatile int i=10;這行代碼有沒有問題?如果沒有,那i到底是什么屬性?

    這個可以同時使用。

    7.空結構體是有大小的

    structstudent

    {

    }stu;

    sizeof(stu)的值是多少呢?在VisualC++6.0上測試一下。

    很遺憾,不是0,而是1。為什么呢?你想想,如果我們把structstudent看成一個模子的話,你能造出一個沒有任何容積的模子嗎?顯然不行。編譯器也是如此認為。編譯器認為任何一種數據類型都有其大小,用它來定義一個變量能夠分配確定大小的空間。既然如此,編譯器就理所當然的認為任何一個結構體都是有大小的.,哪怕這個結構體為空。那萬一結構體真的為空,它的大小為什么值比較合適呢?假設結構體內只有一個char型的數據成員,那其大小為1byte(這里先不考慮內存對齊的情況).也就是說非空結構體類型數據最少需要占一個字節的空間,而空結構體類型數據總不能比最小的非空結構體類型數據所占的空間大吧。這就麻煩了,空結構體的大小既不能為0,也不能大于1,怎么辦?定義為0.5個byte?但是內存地址的最小單位是1個byte,0.5個byte怎么處理?解決這個問題的最好辦法就是折中,編譯器理所當然的認為你構造一個結構體數據類型是用來打包一些數據成員的,而最小的數據成員需要1個byte,編譯器為每個結構體類型數據至少預留1個byte的空間。所以,空結構體的大小就定位1個byte。

    8. 大端與小端

    在x86 系統下,輸出的值為多少?

    #include

    int main()

    {

    int a[5]={1,2,3,4,5};

    int *ptr1=(int *)(&a+1);

    int *ptr2=(int *)((int)a+1);

    printf("%x,%x",ptr1[-1],*ptr2);

    return 0;

    }

    5和0x02000000

    由于x86是小端方式,所以低位內容存放到了低位地址。圖中每一種顏色代筆一個int型的內存分布。&a可以獲得數組a的地址,也就是這兒的0xbfd46624, 所以&a+1的結果應該是0xbfd46638(即圖中最下面紅色部分)。對于代碼中的ptr1由于其為int型指針,所以ptr[-1]的意思應該是取0xbfd46638地址之前的一個整型,即為a數組中的最后一個值5。而在計算ptr2的時候,(int)a是將整型地址a轉換成了一個整型數,這樣(int)a+1的結果就是0xbfd46625,然后再將其轉化為int型指針,這樣利用ptr2獲得的數值就是從0xbfd46625開始的一個整型,即為0x02000000

    10. 花括號

    花括號每個人都見過,很簡單吧。但曾經有一個學生問過我如下問題:

    char a[10] = {“abcde”};

    他不理解為什么這個表達式正確。我讓他繼續改一下這個例子:

    char a[10] { = “abcde”};

    問他這樣行不行。那讀者以為呢?為什么?

    花括號的作用是什么呢?我們平時寫函數,if、while、for、switch 語句等都用到了它,但有時又省略掉了它。簡單來說花括號的作用就是打包。你想想以前用花括號是不是為了把一些語句或代碼打個包包起來,使之形成一個整體,并與外界絕緣。這樣理解的話,上面的問題就不是問題了。

    11.再論 a 和&a 之間的區別

    int main()

    {

    char a[5]={'A','B','C','D'};

    char (*p3)[5] = &a;

    char (*p4)[5] = a;

    return 0;

    }

    int main()

    {

    char a[5]={'A','B','C','D'};

    char (*p3)[3] = &a;

    char (*p4)[3] = a;

    return 0;

    }

    int main()

    {

    char a[5]={'A','B','C','D'};

    char (*p3)[10] = &a;

    char (*p4)[10] = a;

    return 0;

    }

    int a[5][5];

    int (*p)[4];

    p = a;

    問&p[4][2] - &a[4][2]的值為多少?

    12. 用 malloc 函數申請 0 字節內存

    另外還有一個問題:用 malloc 函數申請 0 字節內存會返回 NULL 指針嗎?

    可以測試一下,也可以去查找關于 malloc 函數的說明文檔。申請 0 字節內存,函數并不返回 NULL,而是返回一個正常的內存地址。但是你卻無法使用這塊大小為 0

    的內存。這好尺子上的某個刻度,刻度本身并沒有長度,只有某兩個刻度一起才能量出長度。對于這一點一定要小心,因為這時候 if(NULL != p)語句校驗將不起作用。

    13. 不使用任何變量編寫 strlen 函數

    看到這里,也許有人會說,strlen 函數這么簡單,有什么好討論的。是的,我相信你能 熟練應用這個函數,也相信你能輕易的寫出這個函數。但是如果我把要求提高一些呢:

    不允許調用庫函數,也不允許使用任何全局或局部變量編寫 int my_strlen (char *strDest); 似乎問題就沒有那么簡單了吧?這個問題曾經在網絡上討論的比較熱烈,我幾乎是全程“觀戰” ,差點也忍不住手癢了。不過因為我的解決辦法在我看到帖子時已經有人提出了, 所以作罷。

    解決這個問題的辦法由好幾種,比如嵌套有編語言。因為嵌套匯編一般只在嵌入式底 層開發中用到,所以本書就不打算討論 C 語言嵌套匯編的知識了。 有興趣的讀者,可以查找相關資料。 也許有的讀者想到了用遞歸函數來解決這個問題。是的,你應該想得到,因為我把這 個問題放在講解函數遞歸的時候討論。

    既然已經有了思路, 這個問題就很簡單了。

    代碼如下:

    int my_strlen( const char* strDest )

    {

    assert(NULL != strDest);

    if ('黄频国产免费高清视频_富二代精品短视频在线_免费一级无码婬片aa_精品9E精品视频在线观看