6.1 系统语言——权力的双刃剑
居高者形逸而神劳,处下者形劳而神逸。
——《洪应明·菜根谭》
关键词:系统语言;C族语言;C语言;C++;D语言
摘 要:简谈C;C++和D
预览
通禅悟道者拈花不语,坐井观天者蛙鸣鼓噪。
Java程序员大多被惯坏了,环保意识要淡薄得多。
(指针)用得好可以是削铁如泥的神兵利器,用得不好则可能是自我毁灭的罪恶渊薮。
OOP又不是金子,含量越高越好。试图把一切都装进OOP的箱子里的想法无异于削足适履。
它们(系统语言)的理念是:优化机器的时间而不是人的时间,优化机器的记忆而不是人的记忆;假设编译器是愚蠢的而程序员是聪明的,因此赋予程序员更多的权利、义务与责任。
C++是匹无辔无鞍的野马,看似桀骜不驯,若能顺性而御,必能足踏飞燕,行千里而不劳。
提问
为什么C++不支持自动垃圾回收?
在C++中如何解决内存释放问题?
系统语言有哪些特点?
在不引入OOP的前提下,C语言可以借鉴C++的哪些特征?
D语言比C++有哪些改进?
在电脑性能日益提升的今天,还有必要在乎程序的性能和效率吗?
讲解
教室里,学员们正热火朝天地讨论着流行的编程语言。冒号推门而入,仿佛沸水锅里被浇了一瓢冷水,立刻平静下来。
冒号笑吟吟地看着大家:“怎么不讨论了?”
众人齐道:“该您了!”
“首先须要声明的是,本课评论编程语言,乃是应众位之邀,实非本意。”冒号变得严肃起来,“因为这种评论,不可避免地会带上个人色彩,容易产生误导。有道是,通禅悟道者拈花不语,坐井观天者蛙鸣鼓噪。”
众人迅速自动对号:“合着我们就是一群蛤蟆。”
“这样一来,我的处境就很尴尬了。”冒号自嘲着。
有人在幸灾乐祸地偷笑。
“也罢,即使作蛙鸣,至少也要先跳出井来。”冒号毅然决然地加入了蛤蟆的行列,“要谈,就旗帜鲜明地谈,该赞叹的就赞叹,该鄙视的就鄙视。说些你好我好大家好之类不痛不痒的话,倒不如不说。”
叹号一拍大腿:“好,这样才够痛快!”
问号忍不住问:“您究竟打算比较哪些主流语言呢?”
冒号回答:“就谈谈第1堂课提到的最流行的12种语言吧。按语法特征可将它们分为3类:C族静态语言有5种——C、C++、Java、C#和D;非C族静态语言有两种——VB和Delphi;动态语言有5种——Perl、PHP、Python、Ruby 和JavaScript。”
叹号表示怀疑:“这么多种语言怎么比较得过来?”
冒号解释:“我们主要比较第一类的C族语言,这些也是今后学习的重点,其他的只是泛泛而谈。”
引号猜测:“因为他们更重要?”
“可以这么说。”冒号直截了当,“毋庸讳言,在当今的主流语言中,C族语言应用范围之广、使用人数之多、影响力之巨都是其他类语言所无法比拟的。它们之间的关系从名字上就能看出:C语言的前身是B语言;其后是C++;Java曾被称为C++++--,意思是在C++上增点东西再减点东西;C#中#就是4个叠起的加号[1];最后D语言干脆在字母上进行升级。”
句号推断:“B语言、C语言、D语言,下一个该D++、D#或E语言了。”
谁知冒号却说:“E语言已经有了,与Java的语法很像。甚至F语言也有了,但不是C族语言,而是Fortran族的。这不,微软还在.NET平台上推出了F#语言,不过这里的F指的是‘Functional’,即函数式。”
逗号向往着:“不如直接搞个终极的Z语言,成为全世界程序员的唯一指定语言,多省事!”
“这难度不亚于全人类共用一种语言。”冒号笑道,“愿望是美好的,我们还得面对现实。不扯远了,你们先谈谈一下这些C族语言各自的特点吧。”
众人心想:老冒怎么跟国足一个毛病,老喜欢回传,就是不直接射门,真是急煞人也!
问号拣了个软柿子:“C语言是C族老大,又是唯一的纯过程式语言,当然与众不同啦。”
引号一板一眼:“C++在过程式的基础上又引入对象式和泛型式,同时保持了C的高效性和底层开发能力。”
逗号接道:“Java既继承了C++的优点,又克服了C++的复杂性,虽然底层开发能力有所减弱,但具备平台无关性。”
句号不紧不慢:“C#兼具C++与Java各自的优点,但效率上不如C++,跨平台方面不如Java。”
叹号后悔嘴慢:“剩下一个最陌生的D语言,在第一堂课之前还真没听说过,怎么挤上主流语言位置的?我想……呃,它总该比C++要高级吧。”
冒号评价:“各位谈得虽然简单了些,也算八九不离十吧。下面我稍微展开些来讲。”
此时众人有一个共同的愿望,希望老冒这次能痛快地单刀赴会、直捣黄龙。
冒号似乎看出大家的心思,开始口若悬河:“关于C语言,前面多次提到。这是一把历久弥新的宝剑,一旦出鞘,依旧寒光逼人,锋利无俦。有了它,便如战将有了佩剑,平添一分独闯敌营的胆气。尽管以现代的眼光来看,它存在不少缺点,但即使抛开C语言辉煌的历史不谈,单就其以如此高龄在诸多后辈冲击之下仍屹立不倒而论,让人无法对其多加苛求。”
逗号提出异议:“但语言不是让人崇拜的,而是让人运用的。一门语言无论过去如何荣光,如果不适应现代发展趋势,还是可能被淘汰。”
“说得非常好!”冒号竟然鼓起掌来,“迄今为止,本课堂对于具体知识的讲授不算太多,但一直提倡独立思考,不要盲从权威。如果你们能做到这一点,本班的目标也就实现了一半。回头再说说C语言,它源自Unix操作系统的开发,以其良好的抽象性和可移植性取代了汇编语言作为系统开发语言。因其简洁实用、灵活高效,很快从系统领域发展到其他领域而成为通用语言。随着新兴语言的崛起及硬件性能的大幅提高,C语言的缺点也日益显著:过于宽松的类型检查、容易出错的内存管理、相对贫乏的语言特征,等等。虽然自身还在发展,它的市场份额日益减少乃是不争的事实。但在相当长的时间内,它在其所擅长的领域里仍会占举足轻重的一席之地。如果C能借鉴C++的命名空间、重载、异常处理和STL等非OOP的特征,它的生命力绝不会比任何OOP语言弱。附带说一句,C虽然没有直接支持OOP的语法,但经过适当的设计还是能实现OOP的[2]。”
引号咨询道:“关于C语言的学习,您有何建议?”
“精读K&R的《The C Programming Language》,此书不过200多页,堪称C语言的剑诀。其中的R即Dennis Ritchie,是C的创造者,同时也是Unix的缔造者之一,是真正的大师。如今的大师,同博士、教授、院士等头衔一样,严重地通货膨胀了。”冒号不无感慨。
问号尖锐地问:“C++既保持了C的底层开发能力,又引入了OOP,C的处境想必更加艰难吧?”
冒号坦承:“这是不假。C++成功的一个重要因素是对C语言的兼容,由此吸引了大批的C程序员。但这不是没有代价的,C++在兼容C的同时也保留了C的许多缺陷。Java成功的地方有很多,一个不容忽视的因素是它彻底摆脱了与C兼容的桎梏。由于C++对C的改革不彻底,又过于庞杂,并且效率上不如C,这使得C仍有其生存空间。略有讽刺意味的是,对C++批判最激烈的往往来自C的社区,比如Linux之父Linus Torvalds就曾激烈地批判过C++。”
“Linus?那可是我的偶像呢!”叹号惊讶道。
冒号劝诫:“如果你因为是他的粉丝而后悔学C++,那就是为他人的偏执买单,不管那人名气有多大。”
句号指出:“C++最为人诟病的地方有:语法过于复杂,学习曲线陡、开发效率低;支持的范式过多;OOP不彻底;反射(reflection)功能不足;支持指针操作导致安全隐患;没有自动垃圾回收,容易内存泄漏;没有线程支持;没有丰富的标准库支持图形界面、网络编程等。”
“罪状不少哇!这些说法都有一定道理,但有些也有失公允。且听我一一道来。”冒号当起了辩护律师,“C++比较复杂这点没错,Stroustrup说过一句耐人寻味的话:一种语言不够复杂是因为它还不够成熟。成人肯定比儿童复杂,因为他要承担更大的责任。大家不妨看看Java从1.0到即将问世的7.0、C#从1.0到即将问世的4.0的发展过程,是否应证了这一点?当然,C++的复杂度的确高于其他语言,但如果不执着于奇技淫巧,它绝非高不可攀。C++的开发效率相比Java与C#,差距主要在两个方面:一是标准库不够完善,二是须要手工回收垃圾。关于前者,的确是C++的一大软肋,标准库竟然连企业应用中最常用的图形界面、网络编程、数据库处理、多线程等都不能涵盖,严重障碍了生产力。其实C++也有苦衷,不像Java和C#那样有大公司的鼎力支持,只靠效率极为低下的标准委员会来维护。1998年的一个标准直到2003年还在修订,下一个标准至少要到2009年。连Stroustrup都在哭穷,声称没有足够的人力和时间来开发标准库,可为何广受赞誉的Boost库至今仍徘徊在标准门外?考虑到Boost的创办人大多出自标准委员会,其他无此背景的类库恐怕更难登C++之大堂了。相比之下D语言更惨,虽然天生丽质,苦无豪门青睐,只好一直待字闺中。”
叹号感慨:“金钱才是技术的最大推动力啊!”
“话糙理不糙。”冒号也很无奈,“再来谈谈有关自动垃圾回收的问题。在C++中,程序员也不是非得手工清理垃圾不可的。更好的办法是遵循RAII的惯用法(idiom)[3],通过智能指针(smartpointer)来解决内存释放问题。”
逗号听不明白:“什么是RAII?”
“RAII是Resource Acquisition Is Initialization的缩写,直译为‘资源获取即初始化’。”冒号解释,“其实更准确的叫法应该是RRIF(ResourceRelease Is Finalization),即‘资源释放即终结化’[4]。其思想是:将资源的取放与某一对象的生命周期绑定,初始化对象时获取资源,终结化对象时释放资源。用户代码不再直接管理资源,只须控制相应的对象即可。这样代码得以简化,资源的有效性也得以保障,并且还是异常安全的(exception-safe)[5]。”
问号猜想:“在Java中没有这种用法,是因为它已经有了垃圾回收器吗?”
冒号摇摇头:“问题的关键不在这里。资源不只限于内存,还包括文件、线程锁、数据库连接,等等,这些都不是垃圾回收器所能解决的。看看Java的数据库应用代码吧,对于那些频繁出现的被try/catch/finally包裹的resultset.close()、statement.close()、connection.close(),你是习以为常呢,还是不胜其烦?C#的设计者显然意识到这一点,专门提供了using语句来简化释放资源(IDisposable)的代码。”
逗号眼睛一亮:“如果采用RAII的技巧,这些都可以省去了吗?”
冒号再次摇头:“可惜Java不像C++或D那样,能在栈(stack)上创建对象。栈对象有一个特点,一旦超出其作用范围,便自动释放内存。在此之前会调用析构函数(destructor),后者继而调用释放资源的代码。”
几声叹息清晰可闻。
冒号续道:“另一方面,尽管自动垃圾回收机制逐渐为大众所接受——据说C++0x也将部分地支持它——但这种机制也存在缺陷。比如,一个Java程序如果在某一时段极耗内存,由于自动垃圾回收的不定时性,不能保证及时清理内存,可能会抛出OutOfMemoryError的错误。对于内存有限的系统或实时系统来说,这绝对是一个致命的软肋。C++不支持自动垃圾回收,正是基于这些方面的考虑。Stroustrup非常顾虑自动垃圾回收带来的时间和空间上的过多开销,并且担心它会影响C++完成其所肩负的底层任务。另外,千万不要以为有了自动垃圾回收机制就一劳永逸、万事大吉了。Java程序一样会有内存泄漏,其几率甚至可能比C++的更大,因为C++程序员对此更有戒心,而Java程序员大多被惯坏了,环保意识要淡薄得多。”
问号直奔要害:“您如何看待C与C++中的指针?”
冒号欣然接招:“指针是C与C++最大的特色,其他语言要么不支持,要么支持得有限。C与C++可以说是成也指针,败也指针。用得好可以是削铁如泥的神兵利器,用得不好则可能是自我毁灭的罪恶渊薮。但由于二者定位于系统语言,而指针对于底层操作是必不可少的。同样道理,二者的数据类型的转换比其他静态类型语言更*,也是源出于此。”
句号总结:“能力越大,责任越大,风险越大。”
“正是此意!”冒号重重地敲了一下桌子,“此话既适用于编程语言,也适用于程序员。至于C++缺少对反射功能的支持,也是因为追求效率,不愿在元数据上花时间和空间。说到C++支持的范式过多,程序员过于*,代码不标准难维护,这就如同埋怨餐馆提供的菜式过多以致难以摆出一桌酒席一样可笑。最后,指责C++不是100%OOP的说法更是荒谬至极。OOP又不是金子,含量越高越好。试图把一切都装进OOP的箱子里的想法无异于削足适履。典型的如Java中的Math类,逻辑上压根儿就不存在什么Math对象,清一色的static方法和常量就是最好的讽刺。在C++中只要在math的namespace中定义一些*函数就可以了,自然而简洁。作为一个佐证,Java于J2SE5.0引进了静态导入(staticimport)机制[6],C#也在2.0增加了静态类[7](static class),不仅在形式上简化了代码,也在思维上容忍了非OOP的过程式。”
引号发觉:“您好像把对C++所有的责难都化解了。”
“可恨之人必有可怜之处嘛。”冒号俗语反用,“其实C++仍有不少亟待改进之处,D语言就是很好的启示。D语言的提供了可控制的垃圾回收器;支持线程同步;支持动态数组(dynamic array);支持嵌套函数(nestedfunction);支持契约式设计(design by contract);废除了C与C++ 中令人头痛的头文件(headerfile)等。这些都是C++程序员梦寐以求的特征。”
逗号很奇怪:“为什么D语言名气这么小?”
句号吟道:“千里马常有,而伯乐不常有,大腕伯乐更稀有。”
众乐。
冒号拔高了调门:“既然系统语言主要为底层系统的开发服务,这就决定了它们的理念是:优化机器的时间而不是人的时间,优化机器的记忆而不是人的记忆;假设编译器是愚蠢的而程序员是聪明的,因此赋予程序员更多的权利、义务与责任。无视这种背景和理念而去与其他语言相较,完全是不着筋节,不值一哂。当然这并不排斥系统语言用于应用开发,尤其是C++和D语言。须要强调的是,常见的‘C/C++’的说法很不科学。C与C++虽有千丝万缕的联系,但一个简单,一个复杂;一个纯过程式,一个集过程式、对象式、泛型式和元编程于一体。貌合神离,不宜混为一谈。”
叹号一个问题憋了半天,不吐不快:“我有一个问题:如今电脑性能这么高,C与C++如此强调程序的性能和效率还有必要吗?”
“绝对有必要!”冒号斩钉截铁,“其一,纵向看,用户的耐心与电脑的性能成反比,早年一个386人们就满足了,如今却忍受不了586;以前16M内存就不错了,现在1G都嫌小。其二,横向比,占用资源过多、运行相对缓慢的软件竞争力也低。其三,在一些应用领域如人工智能、大型计算等方面,普通电脑的性能还远远不能满足要求,超级计算机的存在就是明证。其四,仍有些程序跑在资源有限的主机上,比如嵌入式系统。”
引号再次要求:“能推荐一些C++方面的书吗?”
冒号直言相告:“学好C一本书足矣,学好C++即使推荐十本仍有遗珠之憾。可以说C++是苦了编程者,甜了著书人。开个小书单:初级——《C++ Primer》和《Thinkingin C++》;中级——《The C++ Programming Language》和《Effective C++》系列;高级——《The C++ In-Depth》系列。这里还要特别推荐一下《The Design and Evolution of C++》,从中你可以看到 C++的设计和演变的来龙去脉,极具启发性。其他的C++精品书籍还有不少,恕不一一列举了。C++是匹无辔无鞍的野马,看似桀骜不驯,若能顺性而御,必能足踏飞燕,行千里而不劳。”
总结
? C++对C语言的兼容是其成功的一个重要因素,但同时也继承了C的一些缺陷。
? C++设计者没有直接支持自动垃圾回收,是担心它造成过大的时空开销,同时会削弱底层开发能力。
? 除了手工释放内存外,C++提倡运用RAII原则解决包括内存在内的资源管理问题。
? C与C++对指针的全面支持和宽松的类型转换限制,均出于底层系统开发的需要。
? C可以借鉴C++的 命名空间、重载、异常处理和STL等非OOP的特征。
? D语言提供了可控制的垃圾回收器,支持线程同步、动态数组、嵌套函数和契约式设计,并废除了头文件和前置声明(forward declaration)。
? 在程序性能与生产效率之间,系统语言更看重前者,它们在赋予程序员更多的权利的同时,也带给程序员更多的负担。
? 程序的性能和效率永远是重要的。一方面,用户对软件性能的期望越来越高;另一方面,有时硬件性能与软件需求并不匹配:有些应用(如人工智能、大型计算)对程序的性能和效率要求极高,有些系统(如嵌入式系统)的资源十分有限。
参考
[1] Bjarne Stroustrup.The Design and Evolution of C++.Reading, MA:Addison-Wesley,1994.219-222
[2] Bjarne Stroustrup.The C++ Programming Language,Special ed..Reading, MA:Addison-Wesley,2000.364-387
[3] Walter Bright.D Programming Language.http://www.digitalmars.com/d/
[4] Axel Tobias Schreiner.Object-oriented Programming with ANSI-C.http://www.planetpdf.com/codecuts/pdfs/ooc.pdf
插语
[1] 更官方的说法是“#”来自音乐中的高半音符号。
[2] 比如文献[4]设计了一种用C来实现OOP的机制。
[3] RAII是由Bjarne Stro- ustrup首先提出的,参见文献[2]。
[4] 说它更准确,是因为资源获取的代码不一定须要出现在constructor中,但资源释放的代码一定出现在destructor中。
[5] 指即使发生异常(exception)也不会导致资源泄露。
[6] 比如,在静态导入Math类(import static java.lang.Math.*;)后,代码中可以直接调用sqrt、log、max等数学方法,不再需要“Math.”的前缀了。
[7] 静态类只能拥有静态成员,不能实例化,不能被继承,也不能继承除Object之外的类或接口。
欢迎转载,转载时请注明:
本文出自电子工业出版社博文视点(武汉)新书《冒号课堂——编程范式与OOP思想》。