C++不垃圾,只是Java很傲慢

以下内容为转载: http://my.oschina.net/alphajay/blog/3859

《Thinking in C++》及《Thinking in Java》的作者Bruce Eckel向来是个“拥C++反Java”派,他曾经不止一次的提到,C++语言特性的添加有多么的深思熟虑,而Java又是如何的把一些奇怪的东西不停的加进去。Bruce认为,理解语言特性为什么会存在是非常有帮助的。他将其称之为“语言考古学”。

在C++委员会会议上我所能找到的,是C++社区里最聪明的一群人,群英荟萃,为我答疑解惑。我很快意识到,这种方式之好,远超我在任何一门研究生课程中之所得。如果考虑到研究生的机会成本,这还是一笔在财务上要划算得多的生意。

我被深深吸引住了,坚持出席了有大约8年的时间。在我走后,委员会仍继续前行;虽标准仍未制定完成,但彼时Java已经出现了,还有一些其他(语言)的草案也问世了(这是技术刺激成瘾者的毛病——我的确钻研某一门语言,但我也一直在寻找更有生产力的手段:那些前景看起来很光明的语言特性可以毫不费力地分散我的注意力)。

每次大家见面的时候,我都会抛出一列清单,这是我累积下来的有关C++的棘手问题列表。通常我会请他们在几日内予以澄清。出席委员会能看到的最有价值的东西就是这个,当然,还包括得以早早接触到即将公布的新特性。

从长远来看,把语言特性添加进C++的谜团里面并观察它,是一门深奥的学问。现在说三道四是一件很简单的事情,说什么C++太烂了,设计太糟糕了等等。在对C++设计时所受的约束都没有任何理解时,很多人就这样脱口而出了。Stroustrup(这个Stroustrup也就是邀请作者参会的Stroustrup,也就是C++语言的设计师Stroustrup)的约束是,C程序应该稍作改动,或者最好不做改动,就能在C++下编译。且不管这是不是完全合乎逻辑,但它给C程序员提供了一个很好的演进路径。不过这存在较大的局限性,需要把每一项大家抱怨不已的困难特性都一一虚拟化。由于这些特性难以理解,许多人就直接得出结论说C++设计糟糕,而这远非事实。

在语言设计上,Java用傲慢的态度对待这一认识。关于这一点,我在《Java编程思想》及许多博文上都写过了。因此我的长期追随者都知道,由于
Gosling(Java语言之父)和Java语言设计者对C++的否定态度,Java一开始就把我拧到了错误的方向。说实话,我与 Gosling 的首次“邂逅”印象糟糕——那是很多年以前的事了,当时我刚进入第一家公司,第一次开始使用UNIX (Fluke,生产电子测试设备;我在里面做嵌入式系统编程)。有一位软件工程师辅导我,教我使用emacs。不过当时公司里唯一的工具只有Gosling Emacs的商用版(Unipress)。如果你做错了什么,程序会侮辱你,把你叫做火鸡,并把屏幕填满垃圾。这样的东西出现在了一个商用产品上,而我们公司可是花了相当一笔钱的。不消说,等到Gnu emacs变得稳定起来后,公司马上就换到了Gnu emacs上(我见过Richard Stallman。当然,他是个疯狂的家伙。不过他也是绝顶聪明的:他知道当遇到麻烦的时候,你需要的是帮助,而不是羞辱)。

我不知道对Gosling印象的这段形成经历在多大程度上影响了我后面对他工作的看法,但事实上,“我们看见它太差劲了,就决定拿出自己的语言”,对C++的这种态度于事无补。尤其是当我开始在《Java编程思想》的写作过程中把它弄清楚,并屡次发现,那些草率决定的语言特性与库,都不得不予以修订——确实如此,其中的大部分都必须要修订,有些修订还是在程序员已经忍受了多年之后才落实。在许多场合下,Gosling坦诚他们必须快马加鞭,否则就要被互联网革命超越了。

我发现,理解语言特性为什么会存在是非常有帮助的。如果是由大学教授一下子和盘托出,把它们端到你面前,你势必就会构想出这门语言的一个神话,说“
这种语言特性之所以存在,肯定有一些真正重要的原因,这些原因只有创建这门语言的聪明人才能理解,我是理解不了的,我信赖它就是了”。从某方面来说,对语言特性这种基于信仰的接受是一种负担;它阻止你对所发生的事情进行分析和理解。在我的主旨演讲中,我会关注一些特性,并检查一下它们在不同语言中是如何被实现的,以及为什么被实现。

这里就有个例子。对象创建。在C语言中,声明了变量之后编译器就会为你创建堆栈空间(未经初始化,除非你初始化,否则会有垃圾数据)。但是如果你想要动态地做这件事情,你就得使用 malloc() 和 free()这两个标准库函数,还要小心翼翼地手工执行完所有的初始化及清理工作。如果你忘了,就会出现内存泄漏等类似灾难,这是常有的事。


有关动态对象创建:一般来说,编译器将内存分为三部分:静态存储区域、栈、堆。静态存储区主要保存全局变量和静态变量,栈存储调用函数相关的变量、地址等,堆存储动态生成的变量,在c中是指由malloc,free运算产生释放的存储空间,在c++中就是指new和delete运算符作用的存储区域。

因为malloc() 和 free() “仅仅”是库函数,在基本编程课上,应有的相关知识通常没有被传授,令人既疑惑不解又胆颤心惊。当程序员需要分配大量的内存空间时,他们就不去学如何来使用这些函数进行处理,取而代之的是常常就分配一个巨型数组的全局变量了事(不是开玩笑),数组之大,远远超过他们曾自认为所需的空间。程序似乎工作了,再说了,好像谁都不会用到产生越界——因此,当多年之后它的确发生的时候,程序中断了,而某个可怜的家伙就得一头钻进去,把错误在哪里这个谜底给找出来。

Stroustrup认为动态分配需要更简单、更安全——这一块得放到语言核心中,而不是降格为库函数。还必须要与初始化和清理一起协同工作,初始化和清理必须由构造函数和析构函数分别提供,以便为所有对象提供相同的保证。

这个问题是影响了全部C++决策的一块里程碑:对C的向后兼容性。
理想情况下,对象的堆栈(heap)分配可只需忽略即可。但C的兼容性要求进行堆栈(stack)分配,因此必须对heap对象和stack对象进行区分。为了解决这个问题,C++从SmallTalk挪用了new 这个关键字。创建 stack 对象只需声明即可,像这样: Cat x;或者带参数的情况下, Cat x("mittens");。而创建heap 对象时,就使用new,像这样: new Cat x; 或者 new Cat x("mittens");。利用这个约束,我们得到一个优雅而一致的解决方案。

自从判定C++的一切都做得不好且过于复杂之后,Java就产生了。具有讽刺意味的是,Java 决定把 stack 彻底抛弃了(特别是忽略了基本类型上的失败,这点我已经在别的地方指出过了)。真好啊,既然所有对象都是在heap上分配的,区分stack和heap的分配就没有必要了。他们可以轻易说 Cat x = Cat() 或者 Cat x = Cat("mittens")。或者甚至更好地,用联合的类型引用来消除重复(不过那样——还有像闭包(closure)之类的其他特性——就显得“太长”了。因此我们反而离不开Java的平凡版;类型推导已经讨论过了,但我敢打赌那不会发生,也不该发生。因为这会在给Java增加特性的同时带来问题)。

Guido Van Rossum (Python的创建者)采用了一个最小化的方案——经常为人所痛斥的空白的使用,正说明了他对语言简洁性的追求。既然 new 关键字不再必要,他就省去了,好像这样: x = Cat("mittens")。Ruby 也可能用了这种方法,不过Ruby其中一个主要的约束是尽可能追随Smalltalk,因此在Ruby是这样的:x = Cat.new("mittens") 。但Java以贬低C++做事的方式为准则,以至于用了new 这个关键字成了一个谜了。自研究了该语言在其他地方所做的决策后,我的猜测是,
他们是不是从来就没有意识到,这东西根本就是可有可无的

因此这就是我所说的语言考古学的意思。我希望人们能用一个更好的视角来看待语言设计,并在学习一门编程语言时,能有更多的批判性思维过程。

你可能感兴趣的:(java,编程,C++,c,C#)