C++的设计与使用 — 1997年11月3日在圣何塞Computer Literacy书店的演讲 马皓明 翻译 荣耀 指导 幻灯片#1 C++的设计与使用:问答 Stroustrup博士的开篇说明 说明:标准以无记名投票的方式在那个周五被批准,Stroustrup博士的预报也就成为事实。请参阅a press release from the ISO C++ standards committee。 幻灯片#2:总览 编程语言是用来做什么的?
C++的设计标准
一个示例
学习C++ 首先,我准备花20分钟左右的时间来设置几个主题。我会说一下编程语言是用来做什么的,讨论此问题部分原因出于我对人们为编程语言大加争吵一直迷惑不解 — 毕竟它们不过是编程语言而已。然后,我将谈一下我认为C++是什么、它应该是什么。最后,我打算展示一些排序算法代码示例,你可能会在一门C++课程的第一天见到它们,因为我认为在今天的编程和系统构建过程中“教育”是一个绝对关键的议题。 幻灯片#3:为什么每个人都关心实现一个系统所使用的是什么编程语言呢? 用户的观点:
系统提供者/供应商的观点:
程序员的观点:
你或许会考虑一下这个问题:为什么人们关心我使用的是哪种编程语言呢?我的意思是,用户不能看出你使用的是何语言。即便他能,我感觉他也没必要去弄清楚 — 我也没必要知道我的汽车引擎的工作细节。假如我能说清我开的车是什么引擎,那这个引擎一定出了毛病,它以非合理的方式强挤进我的意识。我只想开车,没必要知道控制燃料注射器的程序是用什么编程语言写的。我不关心那些,只要它能工作就行,我希望它价钱便宜,若它能昨天交付就更好了。 系统提供者们有一大串他们感兴趣的问题。某种程度上,提供者与用户类似,不过他们的观察点稍微细致些。 大家都知道,程序员有许多观点,但程序员的观点为什么要紧呢?非程序员为什么有必要关心他们的观点呢?我这么考虑:我们身为程序员应该对此问题有一个不错的回答,假若我们的观点无关紧要的话,那管理者就大可只雇佣些他们找得到的最廉价的人,再明确告诉他们要做什么和怎么去做,问题就解决了。我们若想获得本应属于我们的层次更高的专家地位,就得先回答出这类简单问题。 幻灯片#4:代码风格影响程序结构
要使一门编程语言支持整洁的内部结构,可通过:
我的回答是:代码风格之所以重要是因为它能帮助我们表达程序结构。程序的内部结构是至关重要的。用户看不到它,但它决定着你面对不同计算机、不同用户社群、不同自然语言应该如何维护程序、如何做出改变以适应新环境。若我的程序需要在芬兰运行,那用户可能会对喷涌而出的英文文档非常反感;若我用自己感觉自然亲切的丹麦语来写,那你可能会因难以理解而烦恼。 所有这些议题 — 可维护性、可扩展性、可移植性,都依赖于内部结构。一门语言可以增强程序的内部结构抑或使之模糊混乱。这就是编程技术和编程语言之所以重要的原因。我认为,要使一门编程语言支持整洁的内部结构,可以先让该语言表达这个结构变得更容易。比方说,如果我们不得不用十六进制机器语言编写代码,即便我们有一个想法,无需任何修改就把这个想法体现在代码中是不大可能的。我们不断提升编程语言的层次,并提供更多的便利设施以利于表达结构,由此,设计时构思的结构就会更容易直接体现在实际代码中。 另外还可以将经常性任务自动化。有很多麻烦、无聊的工作,若自己可以不动手就最好别动手。在“远古时代”你必须先手动加载寄存器才能加上值,还必须先说清你加的是整数还是浮点数。你或许在一天内写好了一千条指令,并且它们可以运行,辛苦过后,你或许感觉良好 — 我确实喜欢写汇编代码,然而,假若你一天时间内能写出几千行代码的话,我想那一定是哪儿出了岔子。你该有一个灵巧的小工具帮你在半小时内把它搞定。你不想亲手做简单的机器任务只因为你有办法轻易做好它。 我认为一门编程语言能让你完成的工作必须比这门语言的设计者预想的更多。也就是说,你不可能构建出一门语言,使它内建有你可能需要的一切便利设施。当然不多不少地为你提供需要的东西正是多数商业组织努力去做的。我认为不可能满足人们的全部需求。任何设计者或组织都不可能充分了解系统构建者面对问题时所需要的一切。因此,可扩展性就显得至关重要。 在这种情况下,我称之为“程序员必须创造他们自己的概念”。这些概念通过诸如类、模块、模板(不管在不同的语言中它们叫什么名字)等东西表达。因此,一门语言应该能做到这些,然后就是要让概念清晰可见。 我不知你是否使用过代码生成器,它们允许你从一个非常高的层次开始工作,却能产生出低层代码,然而你不能再从那些低层代码返回到高层形式。在这种情况下,你不得不维护一些特别烦人的由机器生成的低阶语言代码。我可不喜欢费那份精力,因为事实上多数时间我并没在写代码,而是设法弄清楚那些代码要做什么事 — 包括我自己的代码以及别人的代码。 我希望语言能更有助于代码分析。优化器只是分析代码并确定如何从中去掉低效内容的东西。你可以让分析器帮你生成依赖图,你还可以让它帮你找出非典型结构、不合语言习惯的表达式。当构建系统时,一切能获得的帮助你都需要,而通过编程语言可以做出这类分析器来帮助分析你写的代码。 这些就是我要讲的几个最高层、最重要、最基础的议题。一如既往,讨论尤为重要的议题与举手之劳的小事总有点难以区分。那我就降低一个层次来谈谈我稍微了解些的事:C++是如何产生的、为什么。 幻灯片#5 最初的思想:把C作为一门系统编程语言的强势与Simula的程序组织功能结合起来 我对编程语言的评论,基本上就是对C++的最初设想,同时也是我用C++工作几年来的一些结论。在着手设计C++之前我已有了基本思想,而这些年来我又学到很多。 C++的最初理念就是这些。我需要像C那样作为一门系统编程语言的强大,我还需要像Simula那样组织程序的功能。 Simula就是我们今天称作OOP和OOD的多数内容的根源。它是一门非常有意思的语言,如果你回顾历史,就会发现它的设计者(Dahl与Nygaard)的理念非常清晰,不仅对语言议题和编程议题,对设计议题亦是如此。他们将编程语言置于设计技术的情境中予以观察。我认为,在急于推出下一个版本的情况下我们常常会忘记那么做。 当我获得了一些Simula使用经验时,我不幸地发现,用它编写的程序的运行效率实在令我难以承受。于是,我得出结论:我不愿在“编写优雅代码”与“编写高效代码”两者间择一舍一。我需要一种语言既能优雅地解决多数问题,又能让我接受它的效率。C++就是这样的一个尝试。对于我正在进行的工作而言,C++已接近这个理想。 幻灯片#6: C with Classes — 为什么是C? 可用的最佳系统编程语言:
但:改进了静态类型检查 C++诞生之初,我称它为“C with classes”。之所以是C,是因为它是最佳的系统编程语言:高效、可移植、非常灵活。可用,且广为人知。 然而,C当时的知名度并没今天这么高。许多人问:“C?为什么你不像别人那样也用Pascal呢?”我的回答是:“哦,我不喜欢Pascal。它是一件紧身衣。我寻找的不是紧身衣,而是灵活性、高效性、可移植性,等等。”一些人就说:“可是,C比较糟糕。”我就回答说:“不,不。C的某些部分糟糕。我不喜欢它草率的类型检查,我不喜欢它的声明语法,我不喜欢它的内建类型转型规则。但是,这些皆属次要问题。我从不认为这些东西有碍于人们写出一个优秀的程序。相反,我认为过分严格的类型系统才会妨碍人们写出优秀的程序,尤其是Pascal对我有妨碍。所以,C的那些缺点都是你有能力处理好的次要问题。” 不过,我将C with Classes发展到C++最初的工作之一就是改进静态类型系统,因为我确实不想让“sqrt(2)”意味着“段违规”,我希望将运行期错误的发生降至最少。 幻灯片#7:C with Classes — 为什么是Classes? 程序组织
从C with Classes发展到C++的其他部分包括:类、Simula的程序组织方面、“明确概念以编写代码并将概念映射为程序中的类”之观念、接受“一个类就是一种类型”的观点,以及静态检查。 我刚才提到我感觉Pascal的静态检查机制很讨厌且没什么用,而Simula中更强的静态检查机制却从没让我感到烦恼,我花了一段时间才弄明白其中缘由。原因在于Simula的类型系统可扩展而且灵活。因此,静态类型检查并非针对语言设计者制定的关于内建类型的规则,而是针对用户为其自定义类型制定的规则,正是这一点引起了天壤之别。如果你有一个可扩展的系统,你手中就不再是一件紧身衣,而是拥有执行自定规则的能力,这是一个根本性的差异。 幻灯片#8:C++的设计规则
这些年来,出现了相当数量的C++设计规则。你不可能坐下来就埋头设计一门语言。一旦你的语言取得了小小的成功,大家都拥过来,除非确实想给它添两样东西,不然他们都希望语言能够稳定。因此,你不能只是日复一日地设计这门语言。你得慢慢建立起一套赖以生存的规则。这套规则使你能说出这样的话:“是的,您的建议很妙,但我在底部实施的规则是这样的,您的建议与之不大协调。” 我认为理论是对解决问题的一大约束。理论并非你选择试图解决问题的一个好向导。因此,你得找现实的问题,或者你等着现实问题来找你。然后,用一切可找到的相关理论来确保你选择的方案适合解决这个问题。 几年后我们用某些大型机器时可能遭遇一些重大问题,我们得有一些大型语言来应对,还要为那些比“只顾眼前编程的笨家伙”聪明得多的程序员着想。对此人们已经有相当多的思考。但我决定:我得了解我现在的机器,了解我的那些正在写程序的朋友们,了解他们正在处理的问题以及他们使用的系统。或许我能为他们提供些帮助,但我会将遥远的未来留给那些制造水晶球的人们。因此,在设计C++时,我努力处理我所了解的、当前的问题,努力使C++在目前有使用价值。在我开创C with classes六个月后,我开始对首位非科研型用户提供支持。 我决定的另一件事是:我设计的是一门编程语言,而不是一个操作系统,也不是一个文件系统,更不是一个用户界面系统或其他什么东西。有很多问题你不能对C++提出,比如“二进制文件是什么?”那不属于语言议题,而是一个系统议题。另一方面,你可以将C++用在各种系统上。在一个没有操作系统的领域中,你也能用C++编写代码,我发现C++的这方面特性尤其有用。我并不肯定燃料注射器也是用C++编写的,但我知道许多能拿在手上摆弄的小玩意含有C++代码。 幻灯片#9:C++是更好的C
这一切的实际结果即:C++是更好的C,意思是说通常用C做的工作可以用C++来做,且做法更佳,也没额外开销,对你可以做什么也没任何限制。它支持数据抽象 — 一种直接表述概念的思想;它支持面向对象编程 — 使用源自Simula的类继承层次;还支持泛型编程 — 一种将类型和函数以其他类型参数化的能力,此能力非常有用。 幻灯片#10 // 示例程序: 我将向你展示一些非常简单的代码。这是你上一门编程课时可能在第二周要完成的那类练习。如果这门编程课进行得尤其出色,它可能在第二天就会出现。基本内容是:写一个程序读入一些数据,给它们排序并输出平均值和中值。在一门真正的课程上,可能你在此问题之前已做了一些别的练习。我猜你们多数都是专业人员,所以不会有问题。唯一真正的问题是要记住那段日子:你的知识是那么少,甚至编写这么个小程序都是一个挑战。因此,这是个典型的介绍性程序,而且事实上它也是存在于实际代码中的常见工作示例。这就使其成为一个优秀的例子,它是现实问题的一种简化形式。我们从某处读取一些内容,检查这些内容是否正确,然后对这些元素做一些操作。有一项约束前提:假设我们不知道这些元素有多少个。要获取的内容可通过人为键入,或来自文件或者网络。这是一个现实问题。考虑一下以你最钟爱的编程语言你会如何解决这个问题。 下面就是使用C语言时你几乎必定会选择的解决方法。我假设你们都了解C。 幻灯片#11 // C风格解决方法: 你有两种选择。可以使用一个指向buf的指针,然后用malloc给buf分配空间,当你用尽其空间时使用realloc获得更多的空间。我认为这是处理此问题的一种专业手法。而初学者的做法只是简单地在一个有“足够”空间的数组中填充东西然后交叉手指祈祷不要溢出。当然了,类似这样的东西绝不会出现在商业软件中(一声苦笑)。 你有一些变量,比如你读入的double型数据、平均值,还有元素的个数。你读入这些数据并且每次都做检查,然后更新发生了变化的平均值。这非常简单。然后你给这个数组排序并输出中值。现在,若是在你第一门编程课的第二周,或是在讲述一门你未接触过的编程语言课程的第一周,你碰到了这个问题,要想避免溢出还真有那么一些困难。你必须搞清楚如何管理内存,如何扩大输入缓冲区空间或者如何避免溢出。普通的新手头一次难以正确处理,也许第二次还是这样。 要对一个非程序员或没接触过C的人解释qsort,那真是一场噩梦。我只是想给buf排序,对吧?为什么非要解释“n”、“sizeof(double)”和“compare”呢?难道这个机器笨到连如何比较两个double型数据都不懂吗?而答案是:“没错,它就这么笨。”你必须自己编写比较函数,而且比较函数代码的长度几乎就像你在这儿看到的代码一样。下一个问题,机器不知道double的大小吗?哦,你看,事实上qsort()不知道它是在给double型数据排序。现在你真陷入困境了!聪明的学生以为你是在空话连篇。这可不妙。是的,可以这么做,没错,至少已经这么做了一百万次了。但这并不是问题所在。问题在于没有任何明显且合理的理由让我们过早趟进这锅热水中。那么,我来展示一下使用目前标准草案定义的C++是如何处理它的,看看解决这个问题需要我们了解的东西是多么少。 幻灯片#12 // C++风格解法: 首先,你只需获得一个double型的vector并读入数据。每当我们得到一个新数据,就把它添到vector的末尾。确保有足够的空间属于vector份内的事。事实上,那正是它要做的工作,直到你把操作系统中的实际内存和虚拟内存都用尽为止。概括地讲,我所说的是:你有一个vector,你把东西读进来,(当然是)把元素添到那个vector的末尾。然后你为它排序 — 从头排到尾。 这个程序短小、安全、简单,且容易解释。我说它安全意思是指:假若你写的代码很糟糕,它很快返给你一个编译期错误,而不是等到运行期才出错。 大家要问一个问题:“它的效率如何?”我要指出:得到一个vector,然后逐渐扩大它的空间,这样做的代价高于在一个大小固定的缓冲区中添加数据。这样做的代价不会比使用malloc+realloc的解法高太多,但要高一点儿。幸运的是,这里的sort运行速度要比qsort快大约五倍。主要原因是你不必再调用蠢笨的compare函数了,你只需使用语言内建的“小于”操作符即可。 基本上就是这样。你有两个示例。第一个,非常不幸,用的是我们教给C或Pascal程序员的传统方法。第二个是我们现在可以做的。我们遇到的问题之一是只能用那些不那么成比例的语言教授真正优雅、简单的风格。若你是一名LISP或Smalltalk程序员,你就会打着哈欠说:“当然了,我们这样做已经有几十年历史了。”你说的没错,但在标准C++中,你可以使用一个框架来完成以前用C和C++做的所有工作,且无需付出运行期代价,因此这种处理方式正确且能承受。如我先前所说,我不想迫使人们在优雅与高效之间择一舍一。 幻灯片#13
我认为教育是关键 — 不能单靠培训。工作机构喜欢派他们的程序员参加一些为期一周的培训课程。那只能教会他们用C语法代替Pascal语法,或用Pascal语法代替C语法,而不可能让他们掌握任何新技术或不同的思维方式。他们顶多只学会了用不同语言来表达等价的解决方案。换句话说,他们没学到多少东西。 我认为不能单靠培训 — 教育才是关键。不二法门即把重点放在概念与技术上,而把教授语言特性安排在后期。当人们说“我被淹没在语言特性中了”时,通常他们的意思是指:“我试着使用所有特性,可我搞不清应该用它们做些什么。”哦,如果你不知道那些特性有什么用途,又何必去学呢?应该是这样:你先是遇到一个问题,通过考虑得出解决思路,而后再去寻找工具以解决问题。 必须将编程语言的特性放到编程和设计技术的情境中再去观察。初期教学应该以高阶数据结构、算法和标准库为基础。把对低阶设施的理解安排在后期 — 至少对多数人来说这是合理的。那些低阶内容应在基本议题掌握之后再学习。是的,我们能处理好指针、数组和内存管理,但请先让我们学会如何调用一个函数、如何声明一个变量、如何编写一个循环、如何避免陷入麻烦,直到有必要接触那些低阶设施时,再去处理它们吧。 现在我准备回答提问。 提问与回答 Q:为什么C++没有realloc? A:哦,若你硬要用那个本不该用的malloc的话,C++也提供realloc。但基本上讲,在C++中不需要realloc,因为诸如vector和list等标准容器使用了我刚刚向你介绍过的技术来扩展内存空间。因此你不必声明一个简单数组,检查出是否已填满了它的内存空间,然后使用realloc,你只需用一个随需要自动增长的vector就可以了。当然了,这是以与realloc看上去很相似的低阶设施实现的,但它的误用倾向要小得多,通常效率也更高。多数人似乎没意识到realloc有时会移动所有元素 — 若他们数组中存储的是指针,然后realloc此数组,然后他们可能会被狠咬一口,他们会惊异于数组被移动了。因此,没错,你可以在C++中使用malloc/realloc,但是你不该那么做,而且没那个必要,因为有一个更好的设施 — 有更安全、更方便的方法来完成相同的工作。 Q:在LISP语言中有一个概念叫做闭包(closure),意思是说编写一个特殊函数,在生成这个函数的地方有许多词汇上下文(lexical context),它的合理应用是传递此函数…… A:我知道什么是closure,有一些C的方言版本试图通过嵌套函数实现这种思想。C++ — 标准C++ — 没有closure或嵌套函数,而且将来也不会实现它们,至少在未来五或十年不会。一部分难题是怎样定义出充足的合理的情境,另一部分难题是你可能得到太多的情境和晦涩的代码,至少在C++和C的世界里这就是回答,而且对于许多领域这都是合理的答复。另一方面,若存在closure的等价物会有利于许多算法。最简单的情况就是“for_each”,在这个函数中你对一个序列的所有元素作某种操作。另一个例子是计算一系列元素的总和。你在何处传入sum并如何返回它呢?再一个例子是比较两个set。你怎样确定它们的比较标准?在C++中,通常是把一个行为类似函数的对象传递给一个算法,例如for_each、accumulate或compare。标准库以“函数对象”的形式对此提供直接支持,某些人称之为“仿函数(functors)”。它们有多种名称,但基本上讲,你定义的是一个可以用来初始化对象的类。这种初始化显式地为一个对象设定初始状态。换句话说,当函数的调用情境(调用函数时的上下文信息)是影响你工作的唯一难题时,不需要再获取并传递这些情境信息了。用(来自情境中的)一个明确的元素集合初始化一个对象,该元素集合指定了这个对象可引用的内容集合。然后,调用此对象的operator()。在这个算法的末尾,你可以调用为该对象定义好的任何函数来从它身上提取信息。比如说,你能够以这种方式多次迭代来传递大量情境信息。因此,在C++中没有closures,也没有嵌套函数。然而,C++的函数对象能够在许多领域达到相同的目的。函数对象让我们能模拟出一些功能或编程技术,并且的确非常优雅高效。 Q:现在的机器速度很快,运行LISP有多困难呢?另外,Java有一个垃圾回收器,C++的未来版本会不会也提供一个? A:近40年来,机器速度每年都在变得更快。而我的经验是,唯一比硬件周期发展更快的就是人类的期望。我认为在某些领域中抛掉5%、10%、30%、50%的效率是可以接受的,但是在许多领域中那就不能接受。一般说来,我最感兴趣的一直是那种对时间和空间都要求十分苛刻的应用,对那些应用来说,编程语言的基本效率是至关重要的。 本质上说,我认为你的问题包括三个部分。其一是机器效率,其二是LISP,其三是垃圾回收。我想它们是相互独立的问题。我确信今天有许多事都可以用LISP来完成,而在十年前那是不可能的。对于我做多数工作来说,LISP碰巧不是我最喜欢的语言,但硬件的进步显然对所有语言(包扩LISP)都有帮助。我感觉LISP的一些很优秀的方面已被人们静静遗忘了,不然就是出于其他原因LISP已被人们淘汰了。 现在,垃圾回收机制变成了一个更加有意思的问题。若你青睐垃圾回收,通常是因为你的工作能承受得起它。而且我确信能承受得起垃圾回收的应用比人们想象的要多。如果我需要一门带有垃圾回收器的语言,出于个人偏好,我可能会选择C++。C++有非常优秀的可用的垃圾回收器。说它们优秀,意思是与你能得到的任何其他语言的垃圾回收器相比至少同样优秀,并且它们能够工作。 我曾努力让标准委员会将“在C++中可以使用垃圾回收”这个事实写入正文,并阐明两三种明显适合使用垃圾回收机制的情况。有三分之一的C++委员会成员着实迷恋C,他们对此有点“歇斯底里”。因此,“垃圾回收机制在C++中是一个合理的实现技术”这个事实在标准中仍然隐而未明。 C++里没有任何东西妨碍实现垃圾回收。包括我在内的许多人一直在尽力宣传实现了垃圾回收后,它可以为未来15年服务。你可以得到一个相当不错的免费垃圾回收器,如果你需要支持之类的服务的话,也有商业产品供你选用。我猜未来几年内会出现对不同的C++垃圾回收器的充分试用,这种情况已经发生了。 Q:现在,若我进一步谈那个问题……可能C++遗留的最严重缺点就是它脱胎于C这一事实了,C++含有许多让人讨厌的C东西。您是否有过这样的想法:C++完成之后,设计一门后继语言来纠正此类问题?我列在“讨厌清单”顶部的就是指针算术。 A:在C++发展的早期,我曾做过一份幻灯片,包括两个列表:优势及劣势。你会发觉C在两方面都非常显着。人们在使用C++时遇到的一些主要问题无疑与它继承于C的内容有关。另一方面,一些最吸引人们的内容,而且也是人们认为最重要的内容,无疑也是它继承于C的内容。我反对任何不利于C和C++相兼容的东西。我希望C社群对C++能抱持类似的态度。 我没计划转向另外一门语言。我认为那些构建通用编程语言的人必须是狂热的人。本质上讲那个领域的成功几率是零,而如果你不顾及这种几率不均等,仍抱有任何程度的成功期望的话,你就会陷进一堆讨厌的工作中。或者,你就会陷入某种商业性的不幸和蒙蔽大众头脑的大肆宣传之中。我开始这门语言设计工作的唯一理由就是,我原本不打算这样做,我必须为一些问题找出解决方法,这便是C++产生的原因。假若我开始设计另外一门语言,那一定是因为我把自己放进一个洞穴里,除了采取那个办法外我无法找到出口,但我现在并不在那个洞里。 我认为真正的问题是各种整数类型的转换规则,我的“C与C++的讨厌内容列表”把这个问题摆在高高在上的位置。但一个星期之后你就会习惯它的语法。我唯一真正担心的一类人是:不必用typedef或查阅手册就能写出类似“返回一个函数指针的函数定义”这样的东西 — 因有这种“能力”而沾沾自喜的人。当那种能力成为他自命不凡的一项资本时,那必定有什么地方出了问题。我认为C的数组和指针模型事实上基础性非常强,与实际硬件匹配得非常严密,因此我并不特别想把这些内容抛弃。所以,在我做类似的事时会艰难地思考一番,但我不必思考得那么辛苦,因为很可能我不去碰那些东西。 Q:您说过可扩展性对C++以及C++的演化非常重要……您是否考虑过让语言拥有一种真正可扩展的编程结构?比如在Forth语言中,你可以创造自己的if-then。 A:很久前我曾在几种情境下考虑过这些。我曾非常希望把多种控制结构整合起来。当时我在从事并发方面的工作,同时在在考虑实现一种更灵活的表达式语法。因此,我注意过那些能够定制控制结构的语言。这是15年前的事了。我看过设计者们列举的优秀示例。我感觉它们很难读明白 — 很打击读者的信心。我与允许自定义控制结构和运算符的编程语言用户交谈过。他们主要的忠告是:“别那么做。”因此我便开始着手设计一门可扩展但不可变的语言。是的,这是一项深思熟虑之后的决定。 既然这里是一家书店,看来我应顺便说明:我刚回答了一个在这本书(D&E)上没回答的问题。几年前我真正花工夫坐下来,考虑这是什么,为什么会这样,它又是如何产生的,然后把这些写成了这本书。 Q:您怎样比较C++与Java? A:我会回答一个关于Java的问题。我会努力给出一个详尽彻底的回答,而不进行任何形式的长篇讨论。Java出现以前已经有其他语言(Smalltalk、Modula-3、Eiffel等)提供类似的功能,因此Java对于我来说不像对某些人那样新鲜。我不喜欢过分强烈的大肆吹嘘,而我认为Java是漂浮在吹嘘之上。我相当反感公司专有语言,Java就是其中一种。因此这可能导致我的回答带点感情色彩。 我认为Java是一门相当没趣的编程语言,还比不上SmallTalk或Modula-3呢。与SmallTalk、 Modula-3不同的是,Java拥有许多公司的支持,并且赶上了Internet大潮。暂时而言,就我的理解, Java是一个解决方案,它在寻找适宜解决的问题,正巧Internet兴起了。从语言角度看,我常听人说Java与C++是如此的相似 — 只是改正了C++的错误之处。我甚至还听说如果Bjarne不去考虑与C兼容,那么Java就是他设计的编程语言 — 非也! 若你读一下近5年来我写的东西,甚至返回到1985年,就会发现Java缺少编程语言一些真正有用的东西:高效地提供新原语的能力。请试着用Java编写一个新string类来代替已有string(你就会发现Java的限制性)。要有真的局部变量,要能让用户定义类型和内建类型以相同的规则运作,要有标准的类型安全的容器。Java还不完整,它正在成长。Java只是刚上路,途中会添加一些C++已提供的设施 — 就像其他语言的经历一样。 无疑我们将看到Java的大量扩展。我认为Java声称的简单性是没道理的。它比更早的语言简单,但那部分是因为Java是新生且不完整的。其他原因就是:库复杂,则语言就相对简单了。我不相信它的效率能比得上C++的2.5到3成,Java的对象模型就是障碍,即便使用更合理的编译技术也是如此。我也不相信Java的可移植性,因为一旦你使用了系统资源,可移植性就烟消云散了。 这是一个很长的回答,而且对Java不是特别有利。我猜对此我也不必表示太多歉意,因为你知道Java传道者们针对C++写过或说过什么。即便我不能(也不想)通过电视广告对你说,“不要用别的,要用就用我的语言”,那至少请给我两三分钟时间,让我来说出事情的另一方面,这并不过分吧。 C++的一条设计标准就是与其他语言共存。这是Java的设计标准所不曾考虑的 — 或者是它反对的。哦,我来告诉你一个故事。几个星期前我接到一家大出版商代理人的电话,他说:“C++书籍的销售量疯狂上升,不仅仅是你的书,而我正在想,这是Java浪潮中的幸存者吗?”我为不得不向他解释而感到非常悲哀。不是的,并非像他说的那样,因为那个销量肯定还不够。 Q:您预计在未来10到20年什么生产力会大幅提高? A:我预想任何生产力都不会大幅提高。我预见到的是一种稳定的进步。一个个体或一个组织的生产力可能获得大幅提高,当然,那也有赖于你对“大幅提高”的定义。在我的世界里,我认为以相同程度的努力来使出错率减少一半也叫做“大幅提高”,因为那可以将成本缩减一半。我还把“将C++引入90年代的收获期”视作“大幅提高”。要想说明C++在这儿或在那儿的生产力倍增,近来我感觉很容易。但那也有赖于你从哪里开始。我的意思是,如果你在编写C代码,请把C++用作一门更好的C,用vector代替数组,用标准算法代替C库函数,你就有可能在某种程度上使生产力倍增。 不要问我改进有多少个数量级。通过正确处理许多很小的事,你会获得巨大改进。不妨参照一下汽车引擎的例子。不可能因为引擎技术一夜之间的巨大改进而使汽车燃油效率提高。聚沙成塔,集腋成裘。如果你是在寻找“革命”,很简单,你只需从一股狂热转到下一股就行了。 Q:你认为下一步会出现哪些改进? A:那要看你从哪里做起。以我考虑最多的事来说,我仍有大量代码使用了指针和C数组。我想把它们清理一遍。我还有大量代码充斥着全局名字空间。我还希望更多地试验异常机制。正像我一直未能找出一种“在一个现存系统中构建类继承层次”的真正优秀的方法一样,在老代码中引入异常也有些难度,而我很少完全白手起家编写一个系统。 Q:这是一个简单轻巧的问题。当你开始创建这门语言时,你是在何种类型的机器上开发的? A:我开始于一台有着128K内存的PDP 11/70。不久我意识到可能不需要应付那么小的内存。我认为将来人们能够负担得起1Mega和1MIPS,因此我构建C++及其第一个编译器满足那种要求。幸运的是,因为做得太过分了,我的第一个C++编译器可以运行于IBM AT上。我实际上是第一个将C++弄到PC上的人,特别是,我将它弄到了一台AT上。然后我做了一件聪明的事情,我扔掉了port,这样,我就无需在那个环境中维护它,从而可以继续进行真正改善C++及其用法的工作。谢谢。 |