一、
这篇文章是应之前在微博上爆过的下个周末某出版社的线下活动而写的。回顾我和C++在这个世纪的第二个春天开始发生过的种种事情,我发现我并不是用一个正常的方法来学会如何正常使用C++的。我的C++学习伴随着很多其他流行或者不流行的语言。现在手中掌握的很多淫荡的技巧正是因为学习了很多编程语言的缘故,不过这并不妨碍我正常地使用C++来在合理的时间内完成我的目标。
学习C++是一个艰难的过程。如果从我第一次看C++的书算起,现在已经过了11年了。一开始的动机也是很不靠谱的。刚开始我很喜欢用VB6来开发游戏,但是我能找到的资料都是用C++来做例子的,文字部分又不丰富,于是我遇到了很多困难。因此我去三联书店买了本C++的书,想着我如果学会了C++,就可以把这些例子翻译成VB6的代码,然后继续用VB6来写游戏。阴差阳错,我买到的是一本语法手册。不过那个时候我还小,不知道什么是MSDN,也不知道MSDN是可以打印出来卖的:
不过因为C++在当时并不是我学习的重点,于是我就没事的时候翻一翻。我们都知道语言参考手册(MSDN里面叫Language Reference)的顺序都是按照类别而不是教学顺序来排列的。于是当我花了很长时间看完了第一遍的时候,就觉得这本书写的云里雾里。刚开始讲什么是表达式的时候,例子就出现了大量的函数和类这种更加复杂的东西。于是我选择重新看一遍,基本的概念就都知道了。当然这个时候完全不能算“学会C++”,编程这种事情就跟下象棋一样,规则都很容易,但是你想要下得好,一定要通过长期的练习才能做到。
当然,在这段时间里面,我依然是一边看C++一边用VB6来学习编程。初二的时候学校发了QBasic的课本,当时看了一个星期就完全学会了,我觉得写代码很好玩,于是从此就养成了我没事逛书店的习惯(就连长大了之后泡MM也有时候会去书店,哈哈哈哈哈)。值得一提的是,我第二次去书店的时候,遇到了下面的这本书《Visual Basic高级图形程序设计教程》:
在这之前我买到的两本VB6的书都是在教你怎么用简单的语法,拖拖界面。然后就做出一个程序来。那个时候我心目中编程的概念就是写写记事本啊、写字板啊、计算器等等这些东西,直到我发现了这本书。我还记得当时的心情。我在书架上随手翻了翻,发现VB竟然也可以写出那么漂亮的图形程序。
这本书包含的知识非常丰富,从如何调用VB内置的绘图命令、如何调用Windows API函数来快速访问图片,讲到了如何做各种图像的特效滤镜、如何做几何图形的变换,一直到如何对各种3D物体做真实感渲染,甚至是操作4维图形,都讲得清清楚楚。这本书比其他大多数编程读物好的地方在于,读者可以仅靠里面的文字,基本不用看他的代码,就可以学会作者想让你学会的所有东西。因此当我发现我怎么着也找不到这本书的光盘(事实上书店就没有给我)的时候,我并没有感到我失去了什么。这本书的文字部分不仅写得很详细,而且作者还很负责任。作者知道像图形这种对数学基础有一定要求的东西,程序员不一定懂——尤其是我那个时候才上初中,就更不可能懂了——所以在书里面看到一些复杂的数学公式的时候,作者都会很耐心的告诉你这些公式的来源,它们的“物理意义”,有些时候甚至还会推导给你看。因此可以想象,这本书包含的内容也特别的丰富。这导致我在读的时候不断地找资料补充自己的数学知识,从而可以亲自把那些程序写(而不是抄)出来。这个过程一直持续到了我终于不用VB转Delphi,到最后上大学改用C++的那个时候,我终于理解了整本书里面讲的所有内容,给我后面的很多事情打下了坚实的基础。
因为数学知识缺乏的关系,学习这些基础知识又不可能那么快,所以我把一部分时间投入在了游戏开发里面,尝试自己弄点什么出来。毕竟当时对编程有兴趣,就是因为“说不定游戏也可以用代码写出来”的想法,于是我得到了下面的这本书:
这本书是我觉得21天惊天阴谋系列里面唯一一本良心的书。它并没有只是简单的罗列知识,而是教你利用VB6内置的功能搭建从简单到复杂的游戏程序。我第一次看到关于链表的知识就是在这里。可惜在我还没学会如何使用VB6的类模块功能之前,我就已经投向了Delphi,因此并没有机会实践这个知识。不过在此之后,我用VB6写的小游戏,已经尝试把游戏本身的模块(这是VB6的一个功能,就跟namespace差不多)分离,积累一些基础代码。
在这段时间里面,我学习语法都学得很慢。循环甚至是在我用人肉展开循环的方法一行一行复制黏贴出了一个井字棋的AI之后才学会的。后来很晚才学会了写函数,全局变量则更晚了。于是在那个时候我写了很多看起来很愚蠢的代码。曾经我以为一个函数的全局变量在退出函数之后是会保留的,然后对着自己写出来的不能运行的代码感到十分的莫名其妙。还有一次做一个记事本,因为不知道“当前文件路径”要存在什么地方,于是在界面上放了一个Label来放文件名。后来有了雄心壮志,想用VB搞定一个长得像Basic的超简陋的脚本。这当然最后是失败了,但是我依稀记得,我当时取得的成就就是把脚本语言的字符串分割成了一个一个的token之后,保存在了一个表格控件里面,以便之后(后来这个“之后”没写出来)读的时候方便一点。之后还尝试写一个读四则运算字符串计算结果的程序,都是先找最里层的括号,把那条不带括号的简单式子计算完之后,把结果也处理成字符串replace回去。直到整个字符串收敛成一个值为止。一直等到我后来买到了一本系统介绍VB6语法和用法的书之后,我的代码才稍微变得不像猴子打出来的。
在刚开始学编程的时候,基本上都没有什么固定的方向,都是在书店里面碰到什么酒写什么。于是有一次我在书店里看到了《Visual Basic 网络高级编程》
这本书是我在学习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 彻底研究》:
虽然说这本书并没有包含那些深刻的面向对象的知识,但是他详细的介绍了Delphi的语法、基础的类库的用法还有Delphi那套强大的控件库和数据开发的能力。这本书第一次让我知道,Delphi是可以内嵌汇编代码的。这给我对计算机的深入理解打开了一扇门。
学习汇编是一个漫长的过程。这倒不是因为汇编的概念很复杂,而是因为里面的细节实在是太多了。这些知识靠网络上零星的文章实在是无法掌握,于是在常年逛书店的习惯之下,我又遇到了《Windows 汇编语言程序设计教程》。
这本书内容其实并不是很多,但是他给了我一个很好的入门的方法,也讲了一些简单的汇编的技巧,譬如说怎么写循环啊,怎么用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的代码来写要快一些。
纯粹的教程类书籍看多了之后,除了类库用得熟、代码写得多以外,好处并不大。所以当我有一天在书店里发现《凌波微步》的时候,刚翻开好几页,我就被它的内容吸引住了,断然入手。
这本书让我第一次觉得,一个程序写得好和写得烂竟然有如此之大的差别。作者下笔幽默,行文诙谐,把十几个例子用故事一般的形式讲出来。这本书不告诉你什么是好的,而告诉你什么是不好的。每一个案例的开头都给出了写得不好的代码的例子,然后会跟你解释的很清楚,说这么做有什么不好,改要怎么改的同时,为什么好的方法是长那个样子的。这本书也开始让我相信方法论的意义。在这个时候之前,我在编程这个东西上的理论基础基本上就只有链表和排序的知识,其它的东西基本都不懂,但是想做出自己想要做的事情却又不觉得有什么太大的麻烦。甚至我到高三的时候写了一个带指令集和虚拟机的Pascal脚本语言(不含指针)的时候,我连《编译原理》这本书都没有听过。因此以前觉得,反正要写程序,只要往死里写,总是可以写出来的。但是实际上,有理论基础和没有理论基础的程序员之间的区别,不在于一个程序能不能写出来,而在于写出来之后性能是不是好,代码是不是容易看懂的同时还很好改,而且还容易测试。这本书对于我的意义就是给我带来了这么一个观点,从而让我开始想去涉猎类似的内容。
当然,那段时间只是这么想,但是却不知道要看什么。所以在一次偶然之下,我发现了《OpenGL 超级宝典》。当然第一次看的时候还是第二版,后来我又买了第三版。
鉴于以前因为《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高级图形程序设计教程》是一本好书,但这只是一本好的入门书,想要深入了解这方面的内容还是免不了花时间看其他材料的。后来我跟何咏一起做图形的时候,知识大部分来源于论文。不过图像方面,还是下面这本冈萨雷斯写的《数字图像处理》给了我相当多的知识。
这本书的特点是,里面没有代码,我很喜欢,不会觉得浪费钱。不过可惜的是在看完这本书之后,我已经没有真的去写什么图像处理的东西了。后面做软件渲染的时候,我也没有把它当成我的主业来做,权当是消磨时间。每当我找不到程序可以写觉得很伤心的时候,就来看看论文,改改我那个软件渲染器,增加点功能之后,我就会发现一个新的课题,然后把时间都花在那上面。
三、
整个高三的成绩都不错,所以把时间花在编程上的时候没人理我,直到我二模一落千丈,因此在高考前一个月只好“封笔”,好好学习。最后因为失误看错了题目,在高考的时候丢了十几分的原始分,估计换算成标准分应该有几十分之多吧,于是去了华南理工大学。所幸这本来就是我的第一志愿,所以当时我也不觉得有什么不开心的。去了华南理工大学之后,一个令我感到十分振奋的事情就是,学校里面有图书馆,图书馆的书还都不错。虽然大部分都很烂,但是因为基数大,所以总能够很轻松的找到一些值得看的东西。
我还记得我们那一年比较特殊,一进去就要军训。军训的时候电脑还没来得及带去学校,学校也不给开网络,所以那一个月的晚上都很无聊,跟同学也还不熟悉,不知道要干什么。所以那段时间每到军训吃晚饭,我就会跑到学校的图书馆里面泡到闭馆为止。于是有一天让我发现了李维写的这本《Inside VCL》。
虽然到了这个时候我用Delphi已经用得很熟悉了,同时也能写一些比较复杂的程序了,但是对于Delphi本身的运作过程我是一点都不知道。所以当我发现这本书的时候,如鱼得水。这本书不仅内容深刻,更重要的是写的一点都不晦涩难懂,所以我看的速度非常快。基本上每个晚上都可以看100页,连续七八天下来这本书就被我翻完了。这带来了一个副作用就是,图书馆的姐姐也认识我了——当然这并没有什么用。
过后我又在书店得到了一本《Delphi 源代码分析》。
这本书跟《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脚本引擎,所以当我听说我大学的班主任是教编译原理的时候,我就很开心,去跟她交流这方面的内容,把我当时的设想也拿给她看。当然我的设想,没有理论基础的知识,都是很糟糕的,于是班主任就给了我一本《编译原理》。当然,这并不是《龙书》,而是一本质量普通的书。不过当我了解了这方面的内容之后,《龙书》的大名也就进入我的耳朵里了:
由于之前用很愚蠢的方法写了个Pascal脚本的缘故,看《龙书》之后很容易就理解了里面各种精妙的算法在工程上的好处。我之前的作法是先用扫描的方法切下一个一个的token,然后做一个递归来递归去复杂到自己都没法看的一遍扫描生成简单指令的方法来做。程序写出来之后我当场就已经看不懂了。自从看了《龙书》之后,我才知道这些过程可以用token和语法树来对算法之间进行解耦。不过《龙书》的性质也是跟《Visual Basic 高级图形程序设计教程》一样,是入门类的书籍。用来理解一下编译器的运作过程是没问题的,但是一旦需要用到高级的知识。
这个时候我已经初步理解了编译器前端的一些知识,但是后端——譬如代码生成和垃圾收集——却还是一知半解。不过这并不妨碍我用好的前端知识和烂的后端知识来做出一个东西来。当时我简单看了一下Java语言的语法,把我不喜欢的那些东西砍掉,然后给他加上了泛型。Java那个时候的泛型实现好像也是刚刚出现的,但是我不知道,我也从来没想过泛型要怎么实现。所以当时我想来想去做了一个决定,泛型只让编译器去检查就好了,编译的时候那些T都当成object来处理,然后就把东西做出来了。我本来以为我这种偷工减料拆东墙补西墙忽悠傻逼用户的方法是业界所不容的,不过后来发现Java竟然也是那么做的,让我觉得我一定要黑他一辈子。后来我用我做的这个破语言写了一个俄罗斯方块的游戏,拿给了我的班主任看,向她证明她拿给我的书我没有白看。
不过由于受到了Delphi的影响,我并没有在我的C++代码里面使用泛型。当时由于不了解STL,也懒得去看,于是自己就尝试折腾这么几个容器类自己用。现在代码还留着,可以给大家贴一段:
这段代码已经可以作为反面教材使用了。除了基类有一个virtual的析构函数和代码对齐的比较漂亮以外,基本所有的地方都是设计错误的典型表现。为了这段代码的贴图我特地在硬盘里面翻出来了我那个山寨Java脚本的代码,一打开就有一股傻逼的气息扑面而来,截图放进word之后,屏幕犹如溢屎,内容不堪入目。
之所以把代码写成这样,跟Delphi的class不是值类型的这个功能是分不开的。写了几年的Delphi之后,再加上第一次开始写有点小规模的C++程序,我从来没考虑过一个用来new的class是可以创建成值类型的。所以那个时候我一直处于用C++的语法来写Delphi的状态上。当然这样是不对的,但是因为那一段时间运气比较背,好的C++书都没给我碰上,直到我看到了《C++语言的设计和演化》
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++对象模型》里面:
读《深度探索C++对象模型》,不仅仅是为了知道C++在涉及虚拟多重继承基类的成员函数指针结构是怎样的,而且你还可以从中学到很多技巧——当然是指数据结构的技巧。这本书的内容大概分为两个部分。第一个部分就跟需求一样,会跟你介绍C++的对象模型的语义,主要就是告诉你,如果你这样写,那你就可以获得XXX,失去YYY。第二部分就跟实现一样。按照需求来得到一个好的实现总是一个程序员想做的事情,那么这就是个很好的例子。正常使用C++需要的无限智慧,大部分就包含在上面这两本书里面。一旦把这两本书的内容都理解好,以后写起C++的代码都会得心应手,不会被各种坑所困扰,正确审视自己的代码。
文章之前的部分有提到过,让我正视理论和方法论的意义的是《凌波微步》,所以当工具都掌握的差不多的时候,总需要花时间补一补这方面的内容。首当其冲当然就是大家喜闻乐见的《算法导论》了。我记得当时是唐良同学推荐给我的这本书,还重点强调了一定要看原文,因为中文的翻译不行。所以我就在一个春光明媚的早上,来到了广州天河书城,把这本书搞到手。
这本书的封面颜色暗示着你,想读这本书, 应该去一个山清水秀绿荫环绕的地方。事实证明这是对的。在差不多考英语四级的前后,我有一段时间每天都去华南理工大学那个著名的分手亭看这本书。亭子后面是一个湖,前面有很多树和杂草,旁边还有一个艺术学院,充满了人文的气息。在这种地方看《算法导论》,不仅吸收得快,而且过了一年,我真的分手了。
说实话这本书我没有看完,而且那些证明的部分我都跳过了,实在是对这些东西没有兴趣。不过关于数据结构和大部分算法我看得很仔细。于是我在这方面的能力就大幅度提高——当然跟那些搞ACM的人相比反应还是不够快,不过我的志向并不在这里。除此之外,我通过《算法导论》也学到了如何准确的计算一个函数的时间复杂度和空间复杂度。事实证明这个技能十分重要,不仅可以用来找bug,还可以用来面试。
五、
对于一个读计算机的大学生来说,算法懂了,工具会了,接下来就是开眼界了。不过这些东西我觉得是没法强求的,就像下面这本《程序设计语言——实践之路》一样,都是靠运气才到手的——这是一个小师妹送我的生日礼物:
原本学习的汇编也好,VB、Delphi和C++也好,都是同一类的编程语言。这导致我在相当长的时间里面都无疑为编程就差不多是这个样子。直到我看到了《程序设计语言——实践之路》。这本书告诉我,这个世界上除了命令是语言,还有各种不同的编程的范式和方法。于是借着这本书的机会,我了解到世界上还有Prolog、Erlang和Haskell这么美妙的语言。
这对我的触动很大。一直以来我都是用一种编程方法来解决所有我遇到的问题的。然后突然有一天,我发现有很多问题用别的方法来解决更好,于是我就开始去研究这方面的内容。一开始我的认识还是比较浅,应用这些方法的时候还处于只能了解表面的状态,譬如说曾经流行过几天的Fluent Interface,还有声明式编程啊,AOP等等。直到我遇到了这本全面改变我对C++模板看法的书——《Real World Haskell》:
是的,你没看错,是《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同学,他在我需要开阔眼界的时候,给我提供了大量的资料,让我得以快速成长,功不可没。
虚拟机——系统与进程的通用平台
Garbage Collection——Algorithms for Automatic Dynamic Memory Management
高级编译器设计与实现(鲸书)
程序设计语言理论基础
类型与程序设计语言
Parsing Techniques——A Practical Guide
The Implementation of Functional Programming Languages
posted on 2013-03-23 22:35
陈梓瀚(vczh) 阅读(164290)
评论(35) 编辑 收藏 引用 所属分类:
启示