令人遗憾的是,在我得到这本书的同时,Borland也把Delphi独立出来做了一个叫做Codegear的公司,后来转手卖掉了。我在用Delphi的时候还想着,以后干脆去Borland算了,东西做得那么好,在那里工作肯定很开心。我在高中的时候还曾经把Borland那个漂亮的总部的图片给我妈看过,不过她一直以为是微软的。于是我在伤心了两个晚上之后,看了一眼为了做参考我带到学校来的《Visual C++ 5.0语言参考手册》,找了一个盗版的Visual C++ 2005,开始决定把时间投入在C++上面了。于是Delphi之旅到此结束,从此之后,就是C++的时光了。 四、
学习图形学的内容让我学会了如何写一个高性能的计算密集型程序,也让我不会跟很多程序员一样排斥数学的内容。学习Delphi让我开阔了眼界的同时,还有机会让我了解Delphi内部工作原理和细节。这一切都为我之后做那些靠谱的编译器打下了基础。 因为在高三的时候我在不懂得《编译原理》和大部分数据结构的知识的情况下,用Delphi写出了一个Pascal脚本引擎,所以当我听说我大学的班主任是教编译原理的时候,我就很开心,去跟她交流这方面的内容,把我当时的设想也拿给她看。当然我的设想,没有理论基础的知识,都是很糟糕的,于是班主任就给了我一本《编译原理》。当然,这并不是《龙书》,而是一本质量普通的书。不过当我了解了这方面的内容之后,《龙书》的大名也就进入我的耳朵里了: 这个时候我已经初步理解了编译器前端的一些知识,但是后端——譬如代码生成和垃圾收集——却还是一知半解。不过这并不妨碍我用好的前端知识和烂的后端知识来做出一个东西来。当时我简单看了一下Java语言的语法,把我不喜欢的那些东西砍掉,然后给他加上了泛型。Java那个时候的泛型实现好像也是刚刚出现的,但是我不知道,我也从来没想过泛型要怎么实现。所以当时我想来想去做了一个决定,泛型只让编译器去检查就好了,编译的时候那些T都当成object来处理,然后就把东西做出来了。我本来以为我这种偷工减料拆东墙补西墙忽悠傻逼用户的方法是业界所不容的,不过后来发现Java竟然也是那么做的,让我觉得我一定要黑他一辈子。后来我用我做的这个破语言写了一个俄罗斯方块的游戏,拿给了我的班主任看,向她证明她拿给我的书我没有白看。 不过由于受到了Delphi的影响,我并没有在我的C++代码里面使用泛型。当时由于不了解STL,也懒得去看,于是自己就尝试折腾这么几个容器类自己用。现在代码还留着,可以给大家贴一段: 之所以把代码写成这样,跟Delphi的class不是值类型的这个功能是分不开的。写了几年的Delphi之后,再加上第一次开始写有点小规模的C++程序,我从来没考虑过一个用来new的class是可以创建成值类型的。所以那个时候我一直处于用C++的语法来写Delphi的状态上。当然这样是不对的,但是因为那一段时间运气比较背,好的C++书都没给我碰上,直到我看到了《C++语言的设计和演化》 《C++语言的设计和演化》讲的是当年C++他爹发明C++的时候如何对语言的各种功能做取舍的故事。在这个长篇小说里面,C++他爹不厌其烦地说,虽然C++看起来很鸟,但是如果不这样做,那就会更鸟。看完了这本书之后,基本上就剩下不会模板元编程了,剩下的语言的功能都知道在什么时候应该用,什么时候不该用。C++他爹还描述了一些重要的类——譬如说智能指针和STL的迭代器——在语义上的意思。其实这就跟我们在看待C++11的shared_ptr、unique_ptr和weak_ptr的时候,不要去想这是一个delete对象的策略,而是要想这是一个描述对象所有权关系的这么个“关键字”一样。有些时候细节看得太明白,而忽略了更高层次上的抽象,此乃见树木不见森林。 C++知道每一个特性如何正常使用还不够,如果不知道他们是如何实现的,那有可能在非常极端的情况下,写出来的程序会发挥的不好。正如同如果你知道C++编译器、操作系统和CPU内部是如何处理这些东西的细节,如果你顺着他们去写你的程序的话,那性能的提高会特别明显。譬如说在做渲染器的时候,为什么光线追踪要按照希尔伯特顺序来发射光线,为什么KD树可以把每一个节点压缩成8个字节的同时还会建议你按层来排列他们,都是因为这些背后的细节所致。这些细节做得好,渲染器的效率提高一倍是完全没问题的。这些知识固然很多,但是C++的那部分,却包含在了一本《深度探索C++对象模型》里面: 文章之前的部分有提到过,让我正视理论和方法论的意义的是《凌波微步》,所以当工具都掌握的差不多的时候,总需要花时间补一补这方面的内容。首当其冲当然就是大家喜闻乐见的《算法导论》了。我记得当时是唐良同学推荐给我的这本书,还重点强调了一定要看原文,因为中文的翻译不行。所以我就在一个春光明媚的早上,来到了广州天河书城,把这本书搞到手。 说实话这本书我没有看完,而且那些证明的部分我都跳过了,实在是对这些东西没有兴趣。不过关于数据结构和大部分算法我看得很仔细。于是我在这方面的能力就大幅度提高——当然跟那些搞ACM的人相比反应还是不够快,不过我的志向并不在这里。除此之外,我通过《算法导论》也学到了如何准确的计算一个函数的时间复杂度和空间复杂度。事实证明这个技能十分重要,不仅可以用来找bug,还可以用来面试。 五、
对于一个读计算机的大学生来说,算法懂了,工具会了,接下来就是开眼界了。不过这些东西我觉得是没法强求的,就像下面这本《程序设计语言——实践之路》一样,都是靠运气才到手的——这是一个小师妹送我的生日礼物: 这对我的触动很大。一直以来我都是用一种编程方法来解决所有我遇到的问题的。然后突然有一天,我发现有很多问题用别的方法来解决更好,于是我就开始去研究这方面的内容。一开始我的认识还是比较浅,应用这些方法的时候还处于只能了解表面的状态,譬如说曾经流行过几天的Fluent Interface,还有声明式编程啊,AOP等等。直到我遇到了这本全面改变我对C++模板看法的书——《Real World Haskell》: 当我终于明白了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。 六、 |