伴隨我成長的程式設計書

陳梓瀚發表於2013-03-25

  一、

  這篇文章是應之前在微博上爆過的下個週末某出版社的線下活動而寫的。回顧我和C++在這個世紀的第二個春天開始發生過的種種事情,我發現我並不是用一個正常的方法來學會如何正常使用C++的。我的C++學習伴隨著很多其他流行或者不流行的語言。現在手中掌握的很多淫蕩的技巧正是因為學習了很多程式語言的緣故,不過這並不妨礙我正常地使用C++來在合理的時間內完成我的目標。

  學習C++是一個艱難的過程。如果從我第一次看C++的書算起,現在已經過了11年了。一開始的動機也是很不靠譜的。剛開始我很喜歡用VB6來開發遊戲,但是我能找到的資料都是用C++來做例子的,文字部分又不豐富,於是我遇到了很多困難。因此我去三聯書店買了本C++的書,想著我如果學會了C++,就可以把這些例子翻譯成VB6的程式碼,然後繼續用VB6來寫遊戲。陰差陽錯,我買到的是一本語法手冊。不過那個時候我還小,不知道什麼是MSDN,也不知道MSDN是可以列印出來賣的:

image

  不過因為C++在當時並不是我學習的重點,於是我就沒事的時候翻一翻。我們都知道語言參考手冊(MSDN裡面叫Language Reference)的順序都是按照類別而不是教學順序來排列的。於是當我花了很長時間看完了第一遍的時候,就覺得這本書寫的雲裡霧裡。剛開始講什麼是表示式的時候,例子就出現了大量的函式和類這種更加複雜的東西。於是我選擇重新看一遍,基本的概念就都知道了。當然這個時候完全不能算“學會C++”,程式設計這種事情就跟下象棋一樣,規則都很容易,但是你想要下得好,一定要通過長期的練習才能做到。

  當然,在這段時間裡面,我依然是一邊看C++一邊用VB6來學習程式設計。初二的時候學校發了QBasic的課本,當時看了一個星期就完全學會了,我覺得寫程式碼很好玩,於是從此就養成了我沒事逛書店的習慣(就連長大了之後泡MM也有時候會去書店,哈哈哈哈哈)。值得一提的是,我第二次去書店的時候,遇到了下面的這本書《Visual Basic高階圖形程式設計教程》:

image

  在這之前我買到的兩本VB6的書都是在教你怎麼用簡單的語法,拖拖介面。然後就做出一個程式來。那個時候我心目中程式設計的概念就是寫寫記事本啊、寫字板啊、計算器等等這些東西,直到我發現了這本書。我還記得當時的心情。我在書架上隨手翻了翻,發現VB竟然也可以寫出那麼漂亮的圖形程式。

  這本書包含的知識非常豐富,從如何呼叫VB內建的繪圖命令、如何呼叫Windows API函式來快速訪問圖片,講到了如何做各種影象的特效濾鏡、如何做幾何圖形的變換,一直到如何對各種3D物體做真實感渲染,甚至是操作4維圖形,都講得清清楚楚。這本書比其他大多數程式設計讀物好的地方在於,讀者可以僅靠裡面的文字,基本不用看他的程式碼,就可以學會作者想讓你學會的所有東西。因此當我發現我怎麼著也找不到這本書的光碟(事實上書店就沒有給我)的時候,我並沒有感到我失去了什麼。這本書的文字部分不僅寫得很詳細,而且作者還很負責任。作者知道像圖形這種對數學基礎有一定要求的東西,程式設計師不一定懂——尤其是我那個時候才上初中,就更不可能懂了——所以在書裡面看到一些複雜的數學公式的時候,作者都會很耐心的告訴你這些公式的來源,它們的“物理意義”,有些時候甚至還會推導給你看。因此可以想象,這本書包含的內容也特別的豐富。這導致我在讀的時候不斷地找資料補充自己的數學知識,從而可以親自把那些程式寫(而不是抄)出來。這個過程一直持續到了我終於不用VB轉Delphi,到最後上大學改用C++的那個時候,我終於理解了整本書裡面講的所有內容,給我後面的很多事情打下了堅實的基礎。

  因為數學知識缺乏的關係,學習這些基礎知識又不可能那麼快,所以我把一部分時間投入在了遊戲開發裡面,嘗試自己弄點什麼出來。畢竟當時對程式設計有興趣,就是因為“說不定遊戲也可以用程式碼寫出來”的想法,於是我得到了下面的這本書:

image

  這本書是我覺得21天驚天陰謀系列裡面唯一一本良心的書。它並沒有只是簡單的羅列知識,而是教你利用VB6內建的功能搭建從簡單到複雜的遊戲程式。我第一次看到關於連結串列的知識就是在這裡。可惜在我還沒學會如何使用VB6的類模組功能之前,我就已經投向了Delphi,因此並沒有機會實踐這個知識。不過在此之後,我用VB6寫的小遊戲,已經嘗試把遊戲本身的模組(這是VB6的一個功能,就跟namespace差不多)分離,積累一些基礎程式碼。

  在這段時間裡面,我學習語法都學得很慢。迴圈甚至是在我用人肉展開迴圈的方法一行一行復制黏貼出了一個井字棋的AI之後才學會的。後來很晚才學會了寫函式,全域性變數則更晚了。於是在那個時候我寫了很多看起來很愚蠢的程式碼。曾經我以為一個函式的全域性變數在退出函式之後是會保留的,然後對著自己寫出來的不能執行的程式碼感到十分的莫名其妙。還有一次做一個記事本,因為不知道“當前檔案路徑”要存在什麼地方,於是在介面上放了一個Label來放檔名。後來有了雄心壯志,想用VB搞定一個長得像Basic的超簡陋的指令碼。這當然最後是失敗了,但是我依稀記得,我當時取得的成就就是把指令碼語言的字串分割成了一個一個的token之後,儲存在了一個表格控制元件裡面,以便之後(後來這個“之後”沒寫出來)讀的時候方便一點。之後還嘗試寫一個讀四則運算字串計算結果的程式,都是先找最裡層的括號,把那條不帶括號的簡單式子計算完之後,把結果也處理成字串replace回去。直到整個字串收斂成一個值為止。一直等到我後來買到了一本系統介紹VB6語法和用法的書之後,我的程式碼才稍微變得不像猴子打出來的。

  在剛開始學程式設計的時候,基本上都沒有什麼固定的方向,都是在書店裡面碰到什麼酒寫什麼。於是有一次我在書店裡看到了《Visual Basic 網路高階程式設計》

image

  這本書是我在學習VB的過程中最後一本我覺得不錯的書了。雖然VB本身也提供了很多訪問網路資源的控制元件,但是這本書並沒有讓你僅僅會用被人的輪子來寫程式碼,而是一步一步的告訴你這些網路協議的內容,然後讓你用Socket來跟這些伺服器直接互動。我記得我最後成功的做出了一個郵件收發程式,跟聯想1+1系列自帶程式的功能已經可以媲美了。

  二、

  當我發現C++實在是太難,根本沒辦法真的把網上那些C++的程式改成VB之後,我上了高一,接觸了NOI。NOI讓我得到的一個收穫就是,讓我在上了大學之後很堅定的不把時間浪費在ACM上,從而有了很多時間可以搞圖形、編譯器和女同學。參加高中的NOI培訓讓我知道了什麼是資料結構,還有什麼是指標。老師在講Pascal的時候說,要靈活使用指標才可以寫出高效能的程式。這讓我大開眼界,不僅因為VB沒有指標,而且當時用VB寫圖形的程式感覺怎麼樣也快不上去(當然這有大半原因是因為我程式碼寫得爛,不能全怪VB)的同時,還讓我認識了Delphi。Delphi跟VB一樣可以拖控制元件,而且控制元件長得還很像。於是我就抱著試一試的心理,開始學習如何用Delphi來寫程式碼。

  因為有《Visual Basic 高階圖形程式設計教程》的知識作為背景,我很快就掌握瞭如何用Delphi來開發跟圖形相關的程式。那個時候我覺得該做的準備已經準備好了,於是用Delphi寫了一遍我在VB的時候總是寫不快的一個RPG遊戲。這個遊戲雖然不大,但是結構很完整。在開發這個遊戲的過程中,我第一次體驗到了模組化開發的好處,以及積累基礎程式碼對開發的便利性。同時也讓我嚐到了一個難以維護的程式時多麼的可怕。這個遊戲前後開發了八個月,有一半的事件都是在寫程式碼。對於當時的我來說,程式的結構已經過於複雜,程式碼也多到差不多失控的地步了。後來我統計了一下,一共有一萬兩千行程式碼。由於那個時候我的除錯能力有限,而且也不知道如何把程式寫成易於除錯的形式。結果我等到了我的核心部分都寫完了之後,才能按下F9做第一次的執行(!!!)。當然執行結果是一塌糊塗。我花了很大的努力才把搞到能跑。

  由於程式本身過長,我在開發的過程中覺得已經很難控制了。再加上我發現我的同一個模組裡的函式基本上都是下面的形式:

  PrefixFunction(var data:DataStructure, other parameters ...)

  總覺得跟呼叫Delphi的類庫的時候很像。所以我就想,既然程式碼都變成了這樣,那是不是學習物件導向開發會好一點?在這個過程中我有幸遇到了這本《Delphi6 徹底研究》:

image

  雖然說這本書並沒有包含那些深刻的物件導向的知識,但是他詳細的介紹了Delphi的語法、基礎的類庫的用法還有Delphi那套強大的控制元件庫和資料開發的能力。這本書第一次讓我知道,Delphi是可以內嵌彙編程式碼的。這給我對計算機的深入理解開啟了一扇門。

  學習彙編是一個漫長的過程。這倒不是因為彙編的概念很複雜,而是因為裡面的細節實在是太多了。這些知識靠網路上零星的文章實在是無法掌握,於是在常年逛書店的習慣之下,我又遇到了《Windows 組合語言程式設計教程》。

image

  這本書內容其實並不是很多,但是他給了我一個很好的入門的方法,也講了一些簡單的彙編的技巧,譬如說怎麼寫迴圈啊,怎麼用REPZ這樣的字首等等,讓我可以用匯編寫出有意義的程式。彙編和Delphi的結合也促使我開始去思考他們之間的關係,譬如說一段Delphi的程式碼就經是如何對映到彙編上面的。下面發生的一個小故事讓我印象深刻。

  那還是一個,我還很喜歡各種不知所謂的奇技淫巧的日子。有一天我在論壇裡看到有人說,交換兩個integer變數可以用一種奇葩的寫法:

  a:=a xor b;

  b:=b xor a;

  a:=a xor b;

  於是我就理所當然得想,如果我把它改成彙編,那是不是可以更快,並且超過那種需要中間變數的寫法?後來我試了一次,發現慢了許多。這個事件打破了我對會變的迷信,當然什麼C語言是最快的語言之類的,我從此也就以辯證的眼光去看帶了。在接下來的高中生涯裡,我只用了彙編一次,那還是在一個對影象做alpha blending的程式裡面。我要同時計算RGB,但是暫存器每一個都那麼大,我覺得很浪費,於是嘗試用R<<16+G放到一個暫存器裡面,跟另一個R<<16+G相加。中間隔了一個位元組用來做進位的緩衝,從而達到了同時計算兩個byte加法的效果。後來測試了一下,的確比直接用Delphi的程式碼來寫要快一些。

  純粹的教程類書籍看多了之後,除了類庫用得熟、程式碼寫得多以外,好處並不大。所以當我有一天在書店裡發現《凌波微步》的時候,剛翻開好幾頁,我就被它的內容吸引住了,斷然入手。

image

  這本書讓我第一次覺得,一個程式寫得好和寫得爛竟然有如此之大的差別。作者下筆幽默,行文詼諧,把十幾個例子用故事一般的形式講出來。這本書不告訴你什麼是好的,而告訴你什麼是不好的。每一個案例的開頭都給出了寫得不好的程式碼的例子,然後會跟你解釋的很清楚,說這麼做有什麼不好,改要怎麼改的同時,為什麼好的方法是長那個樣子的。這本書也開始讓我相信方法論的意義。在這個時候之前,我在程式設計這個東西上的理論基礎基本上就只有連結串列和排序的知識,其它的東西基本都不懂,但是想做出自己想要做的事情卻又不覺得有什麼太大的麻煩。甚至我到高三的時候寫了一個帶指令集和虛擬機器的Pascal指令碼語言(不含指標)的時候,我連《編譯原理》這本書都沒有聽過。因此以前覺得,反正要寫程式,只要往死裡寫,總是可以寫出來的。但是實際上,有理論基礎和沒有理論基礎的程式設計師之間的區別,不在於一個程式能不能寫出來,而在於寫出來之後效能是不是好,程式碼是不是容易看懂的同時還很好改,而且還容易測試。這本書對於我的意義就是給我帶來了這麼一個觀點,從而讓我開始想去涉獵類似的內容。

  當然,那段時間只是這麼想,但是卻不知道要看什麼。所以在一次偶然之下,我發現了《OpenGL 超級寶典》。當然第一次看的時候還是第二版,後來我又買了第三版。

image

  鑑於以前因為《Visual Basic 高階圖形程式設計教程》的緣故,我在看這本書之前已經用Delphi寫過一個簡單的支援簡單光照和貼圖的軟體渲染程式,於是看起來特別的快。其實OpenGL相比起DirectX,入門級的那部分API(指glBegin(GL_TRIANGLE_STRIP)這些)是做得比DirectX漂亮的,可惜效能太低,沒人會真的在大型遊戲裡使用。剩下的那部分比DirectX就要爛多了。所以當我開始接觸高階的API的時候,OpenGL的低速部分讓我戀戀不捨。OpenGL的程式我一路寫到了差不多要高考的時候。在那之前學習了一些簡單的技巧。上了大學之後,學習了一些骨骼動畫啊、LOD模型啊、場景管理這些在OpenGL和DirectX上都通用的知識,但是卻並沒有在最後把一個遊戲給做出來。

  我最後一次用OpenGL,是為了做一個自繪的C++GUI庫。這個庫的結構比起現在的GacUI當然是沒法。當時用OpenGL來做GUI的時候,讓我感覺到要操作和渲染字串在OpenGL上是困難重重,已經難到了幾乎沒辦法處理一些高階文字效果(譬如RichText的渲染)的地步了。最後只能每次都用GDI畫完之後把圖片作為一個貼圖儲存起來。OpenGL貼圖數量有限,為了做這個事情還得搞一個貼圖管理器,把不同的文字都貼到同一張圖上。做得筋疲力盡之餘,效果還不好。當我後來開發GacUI的時候,我用GDI和DirectX作為兩個渲染器後端,都成功的把RichText渲染實現出來了,我就覺得我以後應該再也不會使用OpenGL了。GDI和DirectX才是那種完整的繪圖API,OpenGL只能用來畫圖,寫不了字。

  有些人可能會覺得,為什麼我會一直在同時做圖形影象、編譯器和GUI的事情。大家還記得上文我曾經說過我曾經用了好久做了一個伊蘇那種模式的RPG出來。其實我一直都很想走遊戲開發的路線,可惜由於各種現實原因,最後我沒有把這件事情當成工作。做出那個RPG的時候我也很開心,絲毫不亞於我畢業後用C#寫出了一個帶智慧提示的程式碼編輯器的那一次。當然在上大學之後我已經覺得沒有一個美工是做不出什麼好遊戲的,但是想花時間跟你一起幹的美工同學又很難找,因此乾脆就來研究遊戲裡面的各種技術,於是就變成了今天這個樣子。當然,現在開發遊戲的心思還在,我想等過些時日能夠空閒了下來,我就來忽悠個美工妹紙慢慢搞這個事情。

  雖然說《Visual Basic高階圖形程式設計教程》是一本好書,但這只是一本好的入門書,想要深入瞭解這方面的內容還是免不了花時間看其他材料的。後來我跟何詠一起做圖形的時候,知識大部分來源於論文。不過影象方面,還是下面這本岡薩雷斯寫的《數字影象處理》給了我相當多的知識。

image

  這本書的特點是,裡面沒有程式碼,我很喜歡,不會覺得浪費錢。不過可惜的是在看完這本書之後,我已經沒有真的去寫什麼影象處理的東西了。後面做軟體渲染的時候,我也沒有把它當成我的主業來做,權當是消磨時間。每當我找不到程式可以寫覺得很傷心的時候,就來看看論文,改改我那個軟體渲染器,增加點功能之後,我就會發現一個新的課題,然後把時間都花在那上面。

  三、

  整個高三的成績都不錯,所以把時間花在程式設計上的時候沒人理我,直到我二模一落千丈,因此在高考前一個月只好“封筆”,好好學習。最後因為失誤看錯了題目,在高考的時候丟了十幾分的原始分,估計換算成標準分應該有幾十分之多吧,於是去了華南理工大學。所幸這本來就是我的第一志願,所以當時我也不覺得有什麼不開心的。去了華南理工大學之後,一個令我感到十分振奮的事情就是,學校裡面有圖書館,圖書館的書還都不錯。雖然大部分都很爛,但是因為基數大,所以總能夠很輕鬆的找到一些值得看的東西。

  我還記得我們那一年比較特殊,一進去就要軍訓。軍訓的時候電腦還沒來得及帶去學校,學校也不給開網路,所以那一個月的晚上都很無聊,跟同學也還不熟悉,不知道要幹什麼。所以那段時間每到軍訓吃晚飯,我就會跑到學校的圖書館裡面泡到閉館為止。於是有一天讓我發現了李維寫的這本《Inside VCL》。

image

  雖然到了這個時候我用Delphi已經用得很熟悉了,同時也能寫一些比較複雜的程式了,但是對於Delphi本身的運作過程我是一點都不知道。所以當我發現這本書的時候,如魚得水。這本書不僅內容深刻,更重要的是寫的一點都不晦澀難懂,所以我看的速度非常快。基本上每個晚上都可以看100頁,連續七八天下來這本書就被我翻完了。這帶來了一個副作用就是,圖書館的姐姐也認識我了——當然這並沒有什麼用。

  過後我又在書店得到了一本《Delphi 原始碼分析》。

image
  這本書跟《Inside VCL》的區別是,《Inside VCL》講的是VCL的設計是如何精妙,《Delphi 原始碼分析》講的則是Delphi本身的基礎設施的內部實現的細節。以前我從來不瞭解也沒主動想過,Delphi的AnsiString和UnicodeString是指向一個帶長度記錄的字串指標,學習了指標我也沒把這兩者聯絡起來(當然這跟我當時還沒開始試圖寫C++程式有關)。於是看了這本書,我就有一種醍醐灌頂的感覺。雖然這一切看起來都是那麼的自然,讓我覺得“就是應該這麼實現的才對”,但是在接觸之前,就是沒有去想過這個事情。

  令人遺憾的是,在我得到這本書的同時,Borland也把Delphi獨立出來做了一個叫做Codegear的公司,後來轉手賣掉了。我在用Delphi的時候還想著,以後乾脆去Borland算了,東西做得那麼好,在那裡工作肯定很開心。我在高中的時候還曾經把Borland那個漂亮的總部的圖片給我媽看過,不過她一直以為是微軟的。於是我在傷心了兩個晚上之後,看了一眼為了做參考我帶到學校來的《Visual C++ 5.0語言參考手冊》,找了一個盜版的Visual C++ 2005,開始決定把時間投入在C++上面了。於是Delphi之旅到此結束,從此之後,就是C++的時光了。

  四、

  學習圖形學的內容讓我學會了如何寫一個高效能的計算密集型程式,也讓我不會跟很多程式設計師一樣排斥數學的內容。學習Delphi讓我開闊了眼界的同時,還有機會讓我瞭解Delphi內部工作原理和細節。這一切都為我之後做那些靠譜的編譯器打下了基礎。

  因為在高三的時候我在不懂得《編譯原理》和大部分資料結構的知識的情況下,用Delphi寫出了一個Pascal指令碼引擎,所以當我聽說我大學的班主任是教編譯原理的時候,我就很開心,去跟她交流這方面的內容,把我當時的設想也拿給她看。當然我的設想,沒有理論基礎的知識,都是很糟糕的,於是班主任就給了我一本《編譯原理》。當然,這並不是《龍書》,而是一本質量普通的書。不過當我瞭解了這方面的內容之後,《龍書》的大名也就進入我的耳朵裡了:

image

  由於之前用很愚蠢的方法寫了個Pascal指令碼的緣故,看《龍書》之後很容易就理解了裡面各種精妙的演算法在工程上的好處。我之前的作法是先用掃描的方法切下一個一個的token,然後做一個遞迴來遞迴去複雜到自己都沒法看的一遍掃描生成簡單指令的方法來做。程式寫出來之後我當場就已經看不懂了。自從看了《龍書》之後,我才知道這些過程可以用token和語法樹來對演算法之間進行解耦。不過《龍書》的性質也是跟《Visual Basic 高階圖形程式設計教程》一樣,是入門類的書籍。用來理解一下編譯器的運作過程是沒問題的,但是一旦需要用到高階的知識。

  這個時候我已經初步理解了編譯器前端的一些知識,但是後端——譬如程式碼生成和垃圾收集——卻還是一知半解。不過這並不妨礙我用好的前端知識和爛的後端知識來做出一個東西來。當時我簡單看了一下Java語言的語法,把我不喜歡的那些東西砍掉,然後給他加上了泛型。Java那個時候的泛型實現好像也是剛剛出現的,但是我不知道,我也從來沒想過泛型要怎麼實現。所以當時我想來想去做了一個決定,泛型只讓編譯器去檢查就好了,編譯的時候那些T都當成object來處理,然後就把東西做出來了。我本來以為我這種偷工減料拆東牆補西牆忽悠傻逼使用者的方法是業界所不容的,不過後來發現Java竟然也是那麼做的,讓我覺得我一定要黑他一輩子。後來我用我做的這個破語言寫了一個俄羅斯方塊的遊戲,拿給了我的班主任看,向她證明她拿給我的書我沒有白看。

  不過由於受到了Delphi的影響,我並沒有在我的C++程式碼裡面使用泛型。當時由於不瞭解STL,也懶得去看,於是自己就嘗試折騰這麼幾個容器類自己用。現在程式碼還留著,可以給大家貼一段:

image

  這段程式碼已經可以作為反面教材使用了。除了基類有一個virtual的解構函式和程式碼對齊的比較漂亮以外,基本所有的地方都是設計錯誤的典型表現。為了這段程式碼的貼圖我特地在硬碟裡面翻出來了我那個山寨Java指令碼的程式碼,一開啟就有一股傻逼的氣息撲面而來,截圖放進word之後,螢幕猶如溢屎,內容不堪入目。

  之所以把程式碼寫成這樣,跟Delphi的class不是值型別的這個功能是分不開的。寫了幾年的Delphi之後,再加上第一次開始寫有點小規模的C++程式,我從來沒考慮過一個用來new的class是可以建立成值型別的。所以那個時候我一直處於用C++的語法來寫Delphi的狀態上。當然這樣是不對的,但是因為那一段時間運氣比較背,好的C++書都沒給我碰上,直到我看到了《C++語言的設計和演化》

image

  C++他爹寫的這本《C++語言的設計和演化》是一本好書,我認為每一個學習C++的人都應該看。本來《C++Primer》也是一本不錯的書,不過因為我陰差陽錯用了《Visual C++ 5.0 語言參考手冊》入門,所以這本書就被我跳過了。一開始C++用得很爛,覺得渾身不舒服,但是有知道為什麼。看了這本書之後很多疑問就解決了。

  《C++語言的設計和演化》講的是當年C++他爹發明C++的時候如何對語言的各種功能做取捨的故事。在這個長篇小說裡面,C++他爹不厭其煩地說,雖然C++看起來很鳥,但是如果不這樣做,那就會更鳥。看完了這本書之後,基本上就剩下不會模板超程式設計了,剩下的語言的功能都知道在什麼時候應該用,什麼時候不該用。C++他爹還描述了一些重要的類——譬如說智慧指標和STL的迭代器——在語義上的意思。其實這就跟我們在看待C++11的shared_ptr、unique_ptr和weak_ptr的時候,不要去想這是一個delete物件的策略,而是要想這是一個描述物件所有權關係的這麼個“關鍵字”一樣。有些時候細節看得太明白,而忽略了更高層次上的抽象,此乃見樹木不見森林。

  C++知道每一個特性如何正常使用還不夠,如果不知道他們是如何實現的,那有可能在非常極端的情況下,寫出來的程式會發揮的不好。正如同如果你知道C++編譯器、作業系統和CPU內部是如何處理這些東西的細節,如果你順著他們去寫你的程式的話,那效能的提高會特別明顯。譬如說在做渲染器的時候,為什麼光線追蹤要按照希爾伯特順序來發射光線,為什麼KD樹可以把每一個節點壓縮成8個位元組的同時還會建議你按層來排列他們,都是因為這些背後的細節所致。這些細節做得好,渲染器的效率提高一倍是完全沒問題的。這些知識固然很多,但是C++的那部分,卻包含在了一本《深度探索C++物件模型》裡面:

image

  讀《深度探索C++物件模型》,不僅僅是為了知道C++在涉及虛擬多重繼承基類的成員函式指標結構是怎樣的,而且你還可以從中學到很多技巧——當然是指資料結構的技巧。這本書的內容大概分為兩個部分。第一個部分就跟需求一樣,會跟你介紹C++的物件模型的語義,主要就是告訴你,如果你這樣寫,那你就可以獲得XXX,失去YYY。第二部分就跟實現一樣。按照需求來得到一個好的實現總是一個程式設計師想做的事情,那麼這就是個很好的例子。正常使用C++需要的無限智慧,大部分就包含在上面這兩本書裡面。一旦把這兩本書的內容都理解好,以後寫起C++的程式碼都會得心應手,不會被各種坑所困擾,正確審視自己的程式碼。

  文章之前的部分有提到過,讓我正視理論和方法論的意義的是《凌波微步》,所以當工具都掌握的差不多的時候,總需要花時間補一補這方面的內容。首當其衝當然就是大家喜聞樂見的《演算法導論》了。我記得當時是唐良同學推薦給我的這本書,還重點強調了一定要看原文,因為中文的翻譯不行。所以我就在一個春光明媚的早上,來到了廣州天河書城,把這本書搞到手。

image

  這本書的封面顏色暗示著你,想讀這本書, 應該去一個山清水秀綠蔭環繞的地方。事實證明這是對的。在差不多考英語四級的前後,我有一段時間每天都去華南理工大學那個著名的分手亭看這本書。亭子後面是一個湖,前面有很多樹和雜草,旁邊還有一個藝術學院,充滿了人文的氣息。在這種地方看《演算法導論》,不僅吸收得快,而且過了一年,我真的分手了。

  說實話這本書我沒有看完,而且那些證明的部分我都跳過了,實在是對這些東西沒有興趣。不過關於資料結構和大部分演算法我看得很仔細。於是我在這方面的能力就大幅度提高——當然跟那些搞ACM的人相比反應還是不夠快,不過我的志向並不在這裡。除此之外,我通過《演算法導論》也學到了如何準確的計算一個函式的時間複雜度和空間複雜度。事實證明這個技能十分重要,不僅可以用來找bug,還可以用來面試。

  五、

  對於一個讀計算機的大學生來說,演算法懂了,工具會了,接下來就是開眼界了。不過這些東西我覺得是沒法強求的,就像下面這本《程式設計語言——實踐之路》一樣,都是靠運氣才到手的——這是一個小師妹送我的生日禮物:

image

  原本學習的彙編也好,VB、Delphi和C++也好,都是同一類的程式語言。這導致我在相當長的時間裡面都無疑為程式設計就差不多是這個樣子。直到我看到了《程式設計語言——實踐之路》。這本書告訴我,這個世界上除了命令是語言,還有各種不同的程式設計的正規化和方法。於是藉著這本書的機會,我瞭解到世界上還有Prolog、Erlang和Haskell這麼美妙的語言。

  這對我的觸動很大。一直以來我都是用一種程式設計方法來解決所有我遇到的問題的。然後突然有一天,我發現有很多問題用別的方法來解決更好,於是我就開始去研究這方面的內容。一開始我的認識還是比較淺,應用這些方法的時候還處於只能瞭解表面的狀態,譬如說曾經流行過幾天的Fluent Interface,還有宣告式程式設計啊,AOP等等。直到我遇到了這本全面改變我對C++模板看法的書——《Real World Haskell》:

image

  是的,你沒看錯,是《Real World Haskell》!Haskell顛覆了我的世界觀,讓我第一次知道,原來程式碼也是可以推導的。說實話我用Haskell用的並不熟,而且我也沒寫過多少個Haskell的大程式,但是Haskell的很多方面我都去花了很長時間去了解,譬如那個著名的Monad。多虧了當時搞明白了Monad,我藉助這方面的知識,理解了《Monadic Parser Combinator》這篇論文,還看懂ajoo那篇著名的面向組合子程式設計系列

  當我終於明白了Haskell的型別推導之後,我終於體會到了Haskell和C++之間的巨大差異——Haskell的程式的邏輯,都是完全表達在函式簽名上的型別裡面,而不是程式碼裡的。當你寫一個Haskell函式的時候,你首先要知道你的函式是什麼型別的,接下來你就把程式碼當成是方程的解一樣,找到一個滿足型別要求的實現。Haskell的表示式一環扣一環,幾乎每兩個部分的型別都互相制約,要求特別嚴格。導致Haskell的程式只要編譯通過,基本上不用執行都有95%的概率是靠譜的,這一點其他語言遠遠達不到。而且Haskell的類庫(Hackage)之多覆蓋GUI、GPU程式、分散式、併發支援、影象處理,甚至是網頁(Haskell Server Page)都有,用來寫實用的程式完全沒問題。之所以Haskell不流行,我覺得僅有的原因就是對初學者來說太難了,但是人們一旦熟悉了C的那一套,看Haskell的難度就更大了,比什麼都不會的時候更大。

  於是回過頭來,模板超程式設計也就變成一個很自然的東西了。你把模板超程式設計看成是一門語言,把“型別”本身看成是一個巨大的帶引數enum的一部分(scala叫case type),於是型別的名字就變成了值,那麼模板超程式設計的技巧,其實就是對型別進行變換、操作和計算的過程。接下來只要會用模板的形式來表達if、while、函式呼叫和型別匹配,那掌握模板超程式設計是順利成章的事情。撇去type traits這些只是模板超程式設計的具體應用不說,只要熟悉了Haskell,熟悉C++的模板語法,學會模板超程式設計,只需要一個下午——就是學會用蹩腳的方法來寫那些你早就熟悉了的控制流語句罷了。

  當模板超程式設計變成了跟寫i++一樣自然的東西之後,我看語言的感覺也變了。現在看到一個程式語言,再也不是學習與發這麼簡單了,而是可以看到作者設計這門語言的時候想灌輸給你的價值觀。譬如說,為什麼C語言的typedef長那個樣子的?因為他想告訴你,你int a;定義的是一個變數,那麼typedef int a;就把這個變數的名字改成了型別的名字。為什麼C語言定義函式的時候,引數是用逗號隔開?因為你呼叫函式的時候,也是用逗號來隔開引數的。這就是語法裡面的一致性問題。一個一致性好的語言,一個有程式設計經驗初學者只要學習到了其中的一部分,就可以推測他所想要的未知特性究竟是如何用於發表達出來的。一個一致性差的語言,你每一次學到一個新的功能或者語法,都是一個全新的形式,到處雜亂無章,讓人無可適從(所以我很討厭go,還不把go的library移植成C++直接用C++寫算了)。

  從此之後,我就從一個解決問題的程式設計師,變成一個研究程式設計本身的程式設計師了。當然我並不去搞什麼學術研究,我也不打算走在理論的前沿——這並不適合我,我還是覺得做一個程式設計師是更快樂一點的。這些知識在我後續學習開發編譯器和設計語言的時候,起了決定性的作用。而且當你知道如何設計一個優美的語法,那麼你用現有的語法來設計一個優美的library,也就不會那麼難了。當然,設計優美的library是需要深入的瞭解正在使用的語言本身的,這樣的話有可能維護這個library的門檻就會提高。不過這沒有關係,這個世界上本來就有很多東西是2000塊錢的程式設計師所無法完成的,譬如維護STL,維護linux核心,甚至是維護Microsoft Office。

  六、

  上面所列出來的書,每一本都是對我有深刻的影響的。當然光有深刻的影響是不夠的,具體的領域的知識,還是需要更多的資料來深入研究,譬如說下面的一個單子,就是我在學習開發編譯器和虛擬機器的時候所看過的。內容都很深刻,很適合消磨時間。在這裡我要感謝g9yuayon同學,他在我需要開闊眼界的時候,給我提供了大量的資料,讓我得以快速成長,功不可沒。

image

虛擬機器——系統與程式的通用平臺

image

Garbage Collection——Algorithms for Automatic Dynamic Memory Management

image

高階編譯器設計與實現(鯨書)

image

程式設計語言理論基礎

image

型別與程式設計語言

image

Parsing Techniques——A Practical Guide

image

The Implementation of Functional Programming Languages

相關文章