範文齋

位置:首頁 > 校園範文 > 讀書筆記

c語言讀書筆記

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

c語言讀書筆記

c語言讀書筆記

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

第1章關鍵字

ster

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

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

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

ic修飾符

(1)修飾變量

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

(2)修飾函數

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

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

語句使用注意

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

在編寫代碼是,要使得正常情況的執行代碼清晰,確認那些不常發生的異常情況處理代碼不會遮掩正常的執行路徑。這樣對於代碼的可讀性和性能都很重要。因爲,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標準。

t與宏

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

編譯器通常不爲普通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 ('' == *strDest)

{

return 0;

}

else

{

return (1 + my_strlen(++strDest));

}

}

第一步:用 assert 宏做入口校驗。

第二步:確定參數傳遞過來的地址上的內存存儲的是否爲''。如果是,表明這是一個 空字符串,或者是字符串的結束標誌。

第三步:如果參數傳遞過來的地址上的內存不爲'',則說明這個地址上的內存上存儲 的是一個字符。既然這個地址上存儲了一個字符,那就計數爲 1,然後將地址加 1 個 char類型元素的大小,然後再調用函數本身。如此循環,當地址加到字符串的結束標誌符''時, 遞歸停止。

當然,同樣是利用遞歸,還有人寫出了更加簡潔的代碼:

int my_strlen( const char* strDest )

{

return *strDest?1+strlen(strDest+1):0;

}

這裏很巧妙的利用了問號表達式, 但是沒有做參數入口校驗, 同時用*strDest 來代替('' == *strDest)也不是很好。所以,這種寫法雖然很簡潔,但不符合我們前面所講的編碼規範。

可以改寫一下:

int my_strlen( const char* strDest )

{

assert(NULL != strDest);

return ('' != *strDest)?(1+my_strlen(strDest+1)):0;

}

上面的問題利用函數遞歸的特性就輕易的搞定了, 也就是說每調用一遍 my_strlen 函數, 其實只判斷了一個字節上的內容。但是,如果傳入的字符串很長的話,就需要連續多次函數調用,而函數調用的開銷比循環來說要大得多,所以,遞歸的效率很低,遞歸的深度太大甚 至可能出現錯誤(比如棧溢出) 。所以,平時寫代碼,不到萬不得已,儘量不要用遞歸。即便是要用遞歸,也要注意遞歸的層次不要太深,防止出現棧溢出的錯誤;同時遞歸的停止條 件一定要正確,否則,遞歸可能沒完沒了。

c語言讀書筆記

我有一本c語言程序設計書,已經靜靜的躺着好長時間了,前幾天朋友說,你這本書有些發黃了,看來是好長時間沒有翻過了。

其實這本書是我初學電腦時朋友給的,但只看了幾頁就放在那兒了,那時沒有自己的電腦,看起來很吃力。在朋友的提醒下,現在可以找一點時間去翻一翻,雖然沒有什麼價值,但學習也是一種樂趣......

記錄第二天:

昨天詳細讀了一下第一章,感覺寫的很詳細,但我不知道書上的知識點是否全正確。

一、數據類型的基本概念

(1)數據類型規定了一個以值爲其元素的集合。

(2)數據類型定義了一個運算集

(3)數據類型定義了數據在計算機內的存儲及書寫中的表示方式。

二、c語言的數據類型

數據包含常量和變量,他們都屬於某個數據類型。

三、常量

常量是程序中其值不發生變化的量,常量有數、字符和字符串。在c中常量不需要類型說明就可以直接使用,除此之後c中還有一種表示常量的形式,稱爲符號常量。

(一)數

1、c中數有整數和小數

(1)整數

整數有十進制,八進制,十六進制,八進制以數字0開頭,十六進制以0x開頭,十進制以1-9中的一個開頭。

整數有正負之分,分別在前面加上+,-符號,正數的符號+可以省略。

整數有短整數,整數,長整數之分,一般短整數16位,在我的機器上是short(16),int(32),long(32),對於整數的取值範圍有符號的(-2的n位次方至2的n位次方減1),無符號的0到2的n位次方-1

長型數的書寫方法:在數後面加一個L,如:0xffL

(2)實數

又叫浮點數,只有十進制的,實數有單精度和雙精度之分,實數有兩種表示形式,一種是小數,一種是指數。

小數:由整數部分,小數點,小數部分。

指數:由尾數(必須有),e/E,指數(必須是整數)

(二)字符常量

字符常量是用一對''括起來的單一字符,一個字符常量在計算機中佔一個字節(8位),字符常量的值就是這個字符在所屬字符集中的編碼(如:ASCII碼)。

字符常量中的單引號用作定界符,所以對於單引號的表示方法就得用另外一種方法表示:''' 用一個斜槓加一個'來轉義一個單引號,對於斜框用來轉義。

字符常量在計算機中是以編碼的方式存放,所以說他實際是一個字節的整數,可以參與各種運算,如+,-,*,/,比較等。

(三)字符串常量

字符串常量是用雙引號護起來的一串字符,字符的個數稱其長度,字符串常量簡稱爲字符串。

長度爲n的字符串在計算機的存儲中佔用n+1個字節,最後多出來的那個存放一個NULL字符,ASCII編碼是0,我們可以用''來表示這個字符。

任何一個字符串在機器內都是以結尾。如:abc,其實是'a','b','c',''的存儲格式。

對於"這個雙引號,由於用作定界符了,所以只能轉義表示:"。

注意: 'A' , "A"這是兩個不同的存儲方式,一個是65,一個是65,0

(四)

一般的字符可以直接寫出,但對於NULL,回車,新行,退格等字符如何書寫?

對於這些字符我們可以用其它的方式表示出來,常用的有:

新行

回車

水平製表

v 垂直製表

 退格

f 換頁

a 響鈴

" 雙引號

' 單引號

NULL

ddd 1到3位八進制數表示一個字符

xdd 1到2位十六進制數表示一個字符

其實ddd,xdd就是字符的編碼。

(五)符號常量

在c語言中可以對常量進行命名,即用符號代替常數值,該符號叫符號常量,符號常量一般用大寫字母表示。

定義格式:

#define 符號常量名 常量

如:

#define NULL 0

#define EOF -1

#define PI 3.1415926

這裏#define是預編譯命令,每一個#define只能定義一個符號常量,且用一行書寫,不用分號結尾。

例:

#include

#define PI 3.1415926

int main(void)

{

float area,r;

printf("請輸入圓的半徑:");

scanf("%f",&r);

area = PI*r*r;

printf("這個圓的面積是:%f", area);

return 0;

}

使用符號常量的好處:

(1)增強程序的可讀性。

如用PI,代表數學中的PI,用EOF代表文件尾,很直觀。

(2)增強程序的可維護性

如果多處用到同一個常量,可以定義符號常量,維護時只修改一處即可。對於擴充和可移植一個程序時大有好處。

四、變量

變量是他的值可以發生變化的一種量,每一個變量都對應計算機中相應長度的存儲單元,以存放變量所取的值。

如:

#include

int main(void)

{

int x = 0; //x的初值爲0

x = 5; //x的值發生了變化

x = 7*5;

x = x+1;

printf("%d",x);

return 0;

}

每一個變量都用一個名字(叫變量名)來表示,變量的名字實際上是內存單元的命名,變量的地址就是該內存單元的開始地址。變量的命名規則同用戶自定義標識符。

任何一個變量都屬於某一數據類型,如果他是整型量,則只能取整數值。

(一)基本數據類型

(1)、從長度上分有8位、16位、32位和64們珠。

(2)、從數據的符號來分,有無符號的,和有符號的。

(3)、按照數學性質來分,分爲整型和實型。

c語言的基本數據類型表:

對於數的值域範圍的算法:

有符號:[-2(位長度-1)次方到 2(位長度-1)次方-1]

無符號:[0到 2位長度次方]

-----------------------------------------------------------------

類型標識符 名字 長度 範圍

char 字符型 8 ASCII字符代碼

unsigned char 無符號字符型 8 0-255

signed char 有符號字符型 8 -127:127

int 整型 16(和環境有關) 範圍算法

unsigned int 無符號整型 同上 同上

signed int 有符號整數 同上 同上

short int 短整型 16(和環境有關) 同上

unsigned short int 無符號短整型 同上 同上

signed short int 有符號短整型 同上 同上

long int 長整型 32位(和環境有關) 同上

unsigned long int 無符號長整型 同上 同上

signed long int 有符號長整型 同上 同上

float 單精度浮點型 32

double 雙精度浮點型 64

void 空類型

-------------------------------------------------------------------

注:

(1)void類型有兩種用法,一是指定函數的返回值的類型,一是用來設置類屬指針。

(2)對於不同的環境,數據表示的範圍不同,可以用sizeof(類型)來測試。

(二)變量的定義

變量的定義就是按照特定的方式爲其指定標識,類型和長度等。程序中所用的每一個變量都必須先定義後引用,沒有定義的變量是不能引用的。定義的目的是爲編譯程序提供所需的信息,以保證完成以下工作。

1、在編譯時根據類型信息來檢查程序中有無非法的數據結構。

2、根據類型信息來檢查對變量施加的運算是否合理。如對兩個浮點數不可以進行%運算。

3、編譯時根據類型和長度信息對變量分配內存,確定數據在內存中的表示方式。

定義:

數據類型 變量或變量表;

如:

int year,date;

int x;

(三)變量的初始化

int x; 對於這樣的變量只是指定了名字和數據類型,並沒有給他初始值,但這並不表示變量沒有值,他的值是當前內存單元中的數據,這個數據是沒有意義的,引用時會產生莫名其妙的結果。

初始化:在定義時直接賦值,則稱爲初始化。

int x = 5;

double y = 3.4;

變量的初始化並不都是在編譯階段完成的,只有靜態變量和外部變量是在編譯階段完成,而局部變量是在運行時初始化的。局部變量的初始化就是一個賦值語句。

int abc()

{

int a =3;

int b =4; //這是在運行時才初始化,而非編譯時。

}

//補充

數據類型的轉換

在c語言的表達式中,准許對不同類型的數值型數據進行某一操作,當不同類型的數據進行操作時,先將其轉換成相同的數據類型,然後再進行操作。

有兩種轉換方式:隱式轉換,顯示轉換。

一、隱式類型轉換

隱式轉換就是在編譯時由編譯程序按照一定規則自動完成。c語言規定的轉換規則是由低級向高級轉換,如果一個操作符帶有兩個不同類型的操作數,那麼在操作之前先將較低的類型向高級轉換,再進行運算。

注意:

1、所有的float類型都轉換成double類型,提高運算精度。

2、在賦值語句中,如果=號左右兩邊的數據類型不同,則將賦值號右邊的值轉換爲賦值號左邊的數據類型。

3、在函數參數傳遞時,也發生數據類型轉換。

4、char short自動轉換成int再參與運算。

規則:

1、char short自動轉換成int

2、float轉換成double

3、低級到高級-> char short ->int ->unsigned-> long ->(float ->double)double

二、顯式轉換

顯式轉換又叫強制型轉換,他不換默認規則,由程序員指定。

如:

int i;

i = i+3.124; //默認是i轉成double,再運算,之後double轉成int賦給i

顯示轉換: i = i+(int)3.124 //先把3.124轉成int,再參與運算。

顯示類型轉換方法:(數據類型)表達式

如:(int) 3.14159; //把double -> int

標籤:讀書筆記 語言