97超级碰碰碰久久久_精品成年人在线观看_精品国内女人视频免费观_福利一区二区久久

c語言讀書筆記

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

c語言讀書筆記

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

c語言讀書筆記

c語言讀書筆記

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

第1章關鍵字

1.register

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

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

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

2.static修飾符

(1)修飾變量

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

(2)修飾函數(shù)

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

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

3.if語句使用注意

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

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

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

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

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

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

void*pvoid;

pvoid++;//ANSI:錯誤

pvoid+=1;//ANSI:錯誤

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

例如:

int*pint;

pint++;//ANSI:正確

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

pvoid++;//GNU:正確

pvoid+=1;//GNU:正確

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

void*pvoid;

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

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

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

5.const與宏

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

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

例如:

#define M 3//宏常量

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

......

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

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

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

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

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

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

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

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

先看看下面的例子:

int i=10;

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

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

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

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

再看另一個例子:

volatile int i=10;

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

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

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

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

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

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

這個可以同時使用。

7.空結構體是有大小的

structstudent

{

}stu;

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

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

8. 大端與小端

在x86 系統(tǒng)下,輸出的值為多少?

#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可以獲得數(shù)組a的地址,也就是這兒的0xbfd46624, 所以&a+1的結果應該是0xbfd46638(即圖中最下面紅色部分)。對于代碼中的ptr1由于其為int型指針,所以ptr[-1]的意思應該是取0xbfd46638地址之前的一個整型,即為a數(shù)組中的最后一個值5。而在計算ptr2的時候,(int)a是將整型地址a轉換成了一個整型數(shù),這樣(int)a+1的結果就是0xbfd46625,然后再將其轉化為int型指針,這樣利用ptr2獲得的數(shù)值就是從0xbfd46625開始的一個整型,即為0x02000000

10. 花括號

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

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

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

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

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

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

11.再論 a 和&a 之間的區(qū)別

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 函數(shù)申請 0 字節(jié)內存

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

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

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

13. 不使用任何變量編寫 strlen 函數(shù)

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

不允許調用庫函數(shù),也不允許使用任何全局或局部變量編寫 int my_strlen (char *strDest); 似乎問題就沒有那么簡單了吧?這個問題曾經(jīng)在網(wǎng)絡上討論的比較熱烈,我?guī)缀跏侨獭坝^戰(zhàn)” ,差點也忍不住手癢了。不過因為我的解決辦法在我看到帖子時已經(jīng)有人提出了, 所以作罷。

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

既然已經(jīng)有了思路, 這個問題就很簡單了。

代碼如下:

int my_strlen( const char* strDest )

{

assert(NULL != strDest);

if ('