学习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,也懒得去看,于是自己就尝试折腾这么几个容器类自己用。现在代码还留着,可以给大家贴一段: