((*p++) < b p b div>
存儲分配管理
書中關於這方面舉了兩個例子,一個是從編譯時就肯定的固定大小的數組中採用棧的形式進行存儲空間管理;另外一個是舉例庫函數malloc的設計思想。關於使用棧的形式進行存儲管理這裏不贅述,詳見Chapter 5.4,這裏重點說一下malloc的設計思想。
儘管分配程序要爲不一樣的對象分配存儲空間,但程序中只會有一個存儲分配程序,卻要處理多種類型的請求,這樣狀況下有兩個問題:1.如何在大多數機器上知足各類類型對象的對齊要求?2.使用什麼樣的聲明可使得分配程序能返回不一樣類型的指針,以此知足不一樣類型請求的處理?在這一方面,棧式存儲管理的缺點立馬就顯現出來了。
malloc的設計思想很巧妙地解決了這兩個問題。對齊方面,它使用聯合(union)來知足對齊要求,代價是犧牲一些存儲空間。第二個方 面,malloc的返回值的類型是void*,這樣在調用malloc時顯示進行類型轉成所須要的指針類型便可,這樣一來,malloc並不須要識別要申請的內存是什麼類型,它只關心內存的總字節數。
另外,malloc不是從一個編譯時就肯定的固定大小的數組中分配空間,而是須要時向操做系統申請,而且是以空閒塊鏈表的方式進行組織的。
緩衝區
我如今感受緩衝區的思想灰常重要,設計緩衝區能夠減小和避免不少繁瑣的操做,常見的就是頻繁的IO操做。這一點我在寫搜索引擎爬蟲時將所爬取的網頁寫入網頁庫的時候體會尤爲深入,而本書的例程再一次加深了我對緩衝區的理解。
有時候,程序並不能肯定已經讀入的輸入是否足夠,除非超前多讀一些輸入。例如從輸入行中讀一些字符合成一個數字(多是整型多是浮點型)就是一例:首先 讀取並去除前導空白,而後一個字符一個字符地讀取,可是咱們並不知道何時讀取中止,例如輸入字符「1314.521ahathinking」,咱們必須讀到字符‘a’才知道數字已經讀取完畢。此時就致使最後有一個字符不屬於當前所要讀入的數,下回讀取時就不能從字符a讀取了,怎麼辦?這時,咱們須要將其壓回輸入中,對代碼其餘部分而言就至關於沒有讀入該字符同樣,如何壓回輸入?共享緩衝區即是一個好方法,即讀取字符時先看緩衝區中是否有字符,若是有讀取,若是沒有再從輸入中讀取。實現見Chapter 4.3中getch和ungetch的實現。
相似的還有一例,就是Chapter 8.5中getc和putc的實現:從文件中讀取或寫入一個字符,源碼中並非每次都從文件中讀寫,這樣的IO操做太頻繁,而是每次讀或寫一大塊內容放入緩衝區,每次先檢查緩衝區剩餘的字符個數,若是>0,則返回下一個字符指針,不然填充緩衝區。
值得一提的是,緩衝區的思想跟棧式的存儲管理有點相似,棧式存儲的那個大數組就至關於預先開闢的緩衝區。
函數設計
關於函數設計,核心問題是如何分解要解決的問題,寫出各個有獨立功能的函數,經過練習該書中的例程(主要是Chapter 5與Chapter 6),你不但能體會到問題的合理分解會讓程序看起來結構明朗,邏輯清晰;更能感到由此帶來的模塊獨立的好處,只要接口設計良好,寫每一個函數都無需過多考慮其餘東西,當全部分解的功能函數完成後,你會發現本來感受一個複雜的問題就這樣被Divide-and-Conquer了。
另外,對於每個函數的設計都要認真思考。如getline函數的實現,讀的過程當中就該思考,若是這個函數讓你來設計,你會如何設計?函數參數如何設計? 返回類型如何定義?是像日常同樣直接返回void類型仍是跟據它未來可能的用途設計一個更爲合理的返回類型?函數功能邏輯應如何實現?是上來就讀取數據仍是考慮去除輸入行的前導空白?有沒有意外狀況?好比讀到空行了怎麼辦?程序結構如何安排?是想來就寫仍是考慮程序結構能夠更爲簡潔地表達?如何讓程序更加 精煉?種種這些,其實都是須要咱們用心去考慮的,不是就簡單一個函數的問題,就像以前說的,雖然簡單,但每一個例子都很經典,值得咱們去學習、更爲重要的是 去思考!
二叉樹
在不知道單詞表的狀況下,統計輸入中全部單詞的出現次數,並分別按字母順序和詞頻降序打印?你會如何設計?
Chapter 6.5例程讓你感覺到二叉樹的強大,不但方便查找,並且單詞自己就是放在正確的位置,而記錄每一個單詞的節點位置指針使得按照詞頻的排序變得簡單。乾貨,值得仔細揣摩。
哈希
你們知道,宏是在預編譯階段進行文本擴展替換的,但你知道編譯器是如何實現宏處理的嗎?或者說宏處理器實現的核心機制是什麼?相似地,編譯器的符號表管理程序是如何實現的?
沒錯,就是哈希,Chapter 6.6的表查找例程給咱們很好地啓發,加深理解哈希的設計思想會有利於解決不少問題,不要不屑於,或許用到的時候就想不到,偏偏就是這樣讓咱們感受很簡單的東西實現了一些讓咱們聽起來是多麼高深複雜的東西。
可變參數列表的設計
這個本書在講printf時候說到了可變參數列表是如何實現的,感受這個應該瞭解下,如今用不到,或許未來工做就會用到了吧,有個概念先。
位字段的妙用
當須要對某些信息進行編碼時,例如對變量的狀態進行編碼(是不是關鍵字,是不是靜態的等Chapter 6.9),對文件指針的狀態進行編碼(是讀的狀態仍是寫的狀態,是否到達文件末,是否發生錯誤Chapter 8.5)等,咱們每每會定義一個與相關位的位置對應的「屏蔽碼」集合,或者利用位字段將幾個屬性集成到一個標識變量中來記錄,從而節省存儲空間(Chapter 6.9)。這樣定義後,在程序中就可使用位操做來驗證相關的屬性值了。
這類型的設計思想在庫中用到的很是多,咱們應該熟悉並學會使用。
最後列舉下幾個經典問題
經過他們的實現來體會解答問題的算法設計思想,下面這幾個題目在經典例程中都有對應。
可變長文本行排序(指針數組)、已知單詞表統計詞頻(折半)、未知單詞表統計詞頻並按字母順序打印(二叉樹)、未知單詞表統計詞頻並按詞頻降序打印(二叉樹、排序)、表查找程序(哈希、鏈表)
===================================================
編程風格
本節羅列一些我的感受在讀本書以後學到的一些小風格,小習慣,每一點舉一個小例子。
程序中最好不要使用突兀的常量,此時應該使用宏定義,並給出註釋,便於別人和本身閱讀;另外宏定義中,若是是表達式,最好外圍加括號(),若是是語句塊,最好使用do { } while (0)。
#define MAXWORD 100 // 一行容許輸入的最大字符數
#define BUFSIZE 1024
#define EOF (-1)
字符變量使用整型來表示,如int c; 由於字符變量存儲時即是以整型存儲的,使用整型表示也能避免沒必要要的問題。
int ch;
scanf("%c",&ch);
函數形參需傳遞數組時,能夠直接將形參定義爲指針類型;由於數組在做爲參數傳遞時會由incomplete type轉爲pointer type。
void test(char s[], int n); // 將其直接定義以下
void test(char *s, int n);
集成數組、指針和地址的算術運算編寫高效精煉的代碼,以下例(能夠嘗試編寫一些經典的庫函數來練習,如字符串處理函數strcpy、strcmp等、內存 操做函數memset等),這方面的思想主要體如今對數組元素進行循環操做的時候。關於這方面有比較容易出錯和被忽視的地方,詳見博文:數組、指針和地址 運算:一個經典的小問題
char * strcpy(char * s1, const char * s2)
{
char * s = s1;
while(*s1++ = *s2++);
return s;
}
若是使用動態申請,則申請後必定要判斷是否申請成功,這是一個好習慣
char * ptr;
if((ptr = (char *)malloc(nbytes)) != NULL){...}
瞭解寄存器變量register與inline都屬於「建議」性關鍵詞,編譯器未必這樣作。注:只有局部自動變量和形參才能夠定義爲寄存器變量;對於循環控制變量及循環體內反覆使用的變量都可定義爲寄存器變量,循環計數是應用寄存器變量的最好候選者
活用位操做,例如在乘除法,取模操做中,見下面例子。關於這方面,詳見文章二進制思考系列博文
i = 879 / 16; // 對比
i = 879 >> 4;
j = 562 % 32; // 對比
j = 562 & (0x1f);
編寫遞歸函數時,static變量是個頗有用的東西,能避免部分參數的傳遞。。這個就不舉例了吧,編程的時候遇到遞歸稍微向這方面思考下就行
===================================================
經典例程
這裏以中文版爲參考,所羅列的例程只是我的認爲比較經典的一部分,許多沒有羅列的習題也是值得一作,有助於理解一些基礎性東西(好比第一章的1-20有助於理解制表符,1-23有助於理解相似「ab\」cd」這樣的特例字符串等)。
P21 讀取一行字符串函數getline 以及書中後續的改進版本
P22 Exer-1-19 反正字符串reverse函數,這個函數很簡單,可是它有一個不是那麼簡單的延伸版本(參見博文關於Reverse Words的思考及三種解法),當初在水木上看到有人推薦的K&R,也是由於這個延伸版本的一道面試題
P38 Exer-2-4 函數squeeze,詳見博文三種方法實現的比較
P41 Exer-2-9求二進制1的個數,該題本博提供了三種方法做爲對比,關鍵是作延伸,深刻理解位操做的一些技巧以及由此衍生的重要數據結構bit-map的實現(見博文位索引),關於位操做我作了一個小總結:二進制思考系列文章
P51-P52 例程atoi和itoa函數,以及後續的改進版本,這兩個函數須要考慮的細節比較多;shellsort函數的實現
P66函數getop函數以及用到的getch和ungetch函數,後兩個函數很實用
P74 函數qsort的實現,達到拿來就能寫的熟練不爲過
P90 庫函數strcpy,strcmp的實現及課後習題
P92 可變長文本行的排序實現,這個應該算很經典了,巧妙使用指針數組排序文本行
P102 函數指針的用途,根據排序的不一樣要求設計不一樣的函數接口
P119 已知單詞表,統計關鍵詞次數,採用指針方式實現的,乾貨,順便複習折半查找
P121 自引用結構中例程,未知單詞表,統計詞頻,按字母順序打印,二叉樹的巧用
P125 Exer-6-4未知單詞表,統計詞頻,按詞頻降序打印,二叉樹與排序的結合
P125 Chapter6.6表查找,體會鏈表、哈希的思想;宏處理器或編譯器的符號表管理的實現機制
注:關於那些知識點方面(如聲明與定義的區別。#define與const及typedef的區別等),本文就不羅列了,須要在編碼過程當中去體會。
歡迎批評補充。