打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
结合自己接触的编程语言,写点最近接触C#与D之后的感想
这个学期开始后,我主要使用的程序语言发生了不小的改变,从以Java为主转移到了以C#为主.然后,在国庆期间开始确实的接触D语言,阅读了语言规范与一些范例代码.生活所迫,现在实在不能继续向D语言投入更多的精力,需要暂时将它再封印起来.但这段时间所看到的和感受到的不能不记录下来,不然时间就真的是浪费了.

这里要记录的,是结合我的个人在编程方面走过的轨迹,观察Java, C#与D的一些特点.只代表从我的角度能看到的状况,有很强的主观性.
同时,推荐对编程语言感兴趣的同好到Lambda the Ultimate - The Programming Languages Weblog去看看.

=======================================================================

先回顾下我学习编程的经历.

最初接触的近乎是编程语言的是LOGO.那是很小的时候,在单色显示器上指挥着小乌龟快乐的画图.然后是QBasic,小学的时候,我也不太记得是以什么为交换条件从Dani那里交换来了一本用QBasic写小游戏的小册子.但我只是粗略的翻了一段时间,虽然也有沉浸在自己写出游戏的幻想中,却一直都没实际去写点什么.
就这么混过了初中和高中,一直到大学进了软件学院,才真正开始学习编程.一上来接触的是计算系统原理课程里里的LC-2的汇编.汇编很直观,有LOGO的基础,对过程式程序的结构还是能轻松理解的.但汇编的表达能力太弱,特别是像LC-2那种最多只有16条指令的体系结构,连一个乘法都得自己写例程,不方便.计算系统原理课程的后半开始教授C语言的基础.真的只是教了基础,连稍微复杂一点的指针都没讲到,主要就是讲完了变量/函数声明,条件/循环控制语句,数组等.这就是我后续学习的基础.一切都是以LC-2的超简化体系结构/指令集与C语言的子集为基础.

大一的第一学期里,在接触到C之后,深感课本里讲的不够用,所以另外找了些书来辅助学习.印象深刻的有清华出版社的谭浩强《C程序设计》,这真的是相当糟糕的一本书.排版问题大概不能怪作者,但内容本身也乱,难以让人系统理解,这大概就怪不了出版社了.这本书跟后来读过的两本谭浩强的书我可能一辈子都忘不了,写得如此糟糕,如同有混乱效果的魔法道具一般.接着,师兄借给我一本由Nell Dale, Chip Weems, Mark Headington写的PROGRAMMING IN C++(SECOND EDITION),感觉就好多了,总算把我带到了C++的门口.这本书的最后稍微带过了些面向对象相关的知识,但是太简短,即使说了ADT啊class之类的概念,还是没办法让我捉摸到头绪.

如是来到了大一的第一学期的结束.读完一次PROGRAMMING IN C++后,我只对C++有模糊的感觉,不知道自己知道什么,更不知道不知道什么.甚至连C与C++的关系到底该如何看待,它们之间的区别的细节如何都没摸清.Anyway,通过课程和自学掌握了基本的C的语法后,我觉得很是鼓舞,可以拿来写点什么好玩的了.就用最原始的方式写了个扫雷来玩.没有用Borland C的图象库,也没有用Curses,只有最原始的界面:以*来显示未打开的位置,#表示旗子,X表示踩到雷,数字表示周围的雷数,然后提示用户输入指令来指定操作类型与参数(坐标).写出来之后很是有成就感,虽然实现的深度优先遍历算法效率很低,虽然界面很糟糕...嘛,更糟糕的是代码的组织方式,大概所有新手都会这样,把整个程序放在一个源文件里,拼命往里塞功能,塞得到后来自己都不想再碰那文件了.

大一的第二学期开始,课程里出现了正式的教授编程语言的课程,这门课同时也是我们课程中最初系统教授面向对象概念的一门.与高一年级的前辈的课程不同,他们用的是C++,而从我们这届开始换成了Java.单以教授面向对象概念这点说,这个改变应该是个不错的决定,再怎么说Java也是更纯粹的面向对象语言.学习到Java,并接触到Swing/JFC之后,觉得可以把以前的扫雷拖出来大改造一番了.要改变程序结构,让它更"面向对象";要改变交互方式,让用户能用图形用户界面(GUI)来游戏;要改进xxx,yyy,zzz...

我将扫雷区(field)设计为装有每个坐标所表示的单元(cell)的容器和遍历雷区状态的操作的对象,然后每个cell是一个包含显示(一个JLabel)/数据(一个int)和一些相关操作的对象.实现出来的改进版扫雷确实能运行,而且也有图形界面,而且也用上了时髦的"对象"概念.我沾沾自喜觉得自己在这种简单小程序的设计上已经很不错,不料却踩到了一个大陷阱:那是一个各功能间高度耦合的设计.这对OO模糊认识下的设计显然是很糟糕的.

到大二的第一学期,我们开始上C++的课程.说真的,还是没教得太深入.C++太大了,支持多种编程范型也意味着许多使用C++的人只能用到C++的很小一部分.一个只用C++中的C子集编程的人与一个专攻C++ metaprogramming的人之间或许就没任何共同语言,虽然他们用的都是C++.我们的C++课程基本上教到基本的面向对象概念,重载new/delete操作符之类的,就没再讲了.像template,STL那些常用的东西都得靠自学.GUI相关的库一个也没教,不知道高一年级的前辈用MFC是不是都是自学的.

=======================================================================

Java

虽然那时课程教的是C++,但我从大一结束的时候开始直到这学期开始前的时间我都是以Java为主要的编程语言.最直接的原因就是,我懒,有很直观又方便的库,再加上有自动内存管理,就这么用了下来.后来其实也知道C++里方便的库也不少,即使要写GUI程序,用wxWidgets,QT,GTK这些都行,也不是一定要用MFC;想要自动内存管理也不是没有,但就是懒得换回来了.加上后面的很多课程都是以Java为范例语言,更是让我懒得换了.

大一结束后的那个暑假,开始与Meta小组一起参加一些程序设计相关的比赛.其中也是以用Java为主——J2EE项目嘛.后来参与的学院里的一个项目也是J2EE项目,可惜那项目在我手上毁了,真的,可惜,也后悔怎么就不负责点把它做好.虽然项目毁了,我积累到的经验还是不少.那是我参与过的最大规模的东西了,无论是从代码行数,还是系统涉及的方面都是.从下面的数据库,到ORM的DAO,到中间的业务逻辑,到上面的控制逻辑,最后到顶上的表现层,然后背后的服务器配置等.应该说,我看到了使用Java来开发这样的系统不错的开发效率,在做一些中型规模的项目时还是很合适的;也看到了一些不足的地方,主要还是"繁琐".

课外时间做的一些逆向工程也有不少是用Java来实现的,但这时Java就显得很笨拙.因为我要做的工具流程很清晰,规模也很小,说实话OO插不上什么手,我最后写出来的东西也不过是object-based而不是object-oriented的.Java的OO长处没发挥出来,底层控制力薄弱的弱点却出来了.这种情况下我依然使用Java的原因不过是一种惯性的推动,加上标准库中String和regex的功能还不错,效率低一点也罢了.我也尝试过别的可能,例如换回用C,或者用Python来做这些小工具,确实是要比Java顺手.纯粹是人的惰性,让我懒得换语言.

不过,真要说我对Java有什么使用上的抱怨的话,首先就是对Swing/JFC很不满.一开始只是做些小游戏什么的,还没太觉得,后来想写点稍微复杂点的GUI就来问题了.Swing的布局非常的不好用.除非能接受一般的BorderLayout,BoxLayout,FlowLayout或者GridLayout提供的简单布局,又或者愿意手工实现一些布局的相关操作,不然GridBagLayout或许就是Swing库中唯一现成可用的布局管理器.很可惜,它的实现是失败的.我已经数不清大二时我为了修理一个用了这布局的界面而有多少个晚上没睡着了.举个例子来说,请看下面使用了GridBagLayout的这个窗口:

在它的初始大小上很正常,一点问题都没有.但要是想稍微把窗口变大一点...:

唔,貌似有点不对? 再拉一下就...:

就变成这样了.我试过把初始大小设大一点看看,结论是初始大小有多大都没关系,都能正常显示;但是拖动之后就没保证了.我肯定我设置的GridBagConstraints符合JavaDoc上所描述的规定,但一拉伸就总会RP.本来我就是因为懒得手写在窗口resize时要做的每个组件的大小重计算和更新,但用GridBagLayout自身提供的resize行为却不对.当时我实在搞不定但作业又到期了,索性把窗口大小给锁住,于是也不需要任何布局管理器,直接把坐标和大小硬编码到程序里就完事.要是谁能指出我的代码的错误,请与我联系,不尽感激 ^ ^
除了布局外,Swing还有众多RP的地方,最典型的两个或许是: 内存泄漏; Look and Feel做得感觉很怪,不够本地化.前者是一直没彻底解决的问题,而后者在Java SE 6里似乎好了些,不过要"根治"也没什么希望.

看到这里,应该会有人要开始推销SWT了.不错,SWT在许多方面比Swing好了不少,不过那布局管理器也很难用,要做到自己满意的效果还是需要花点工夫.外加,无论是SWT还是Swing,要做透明窗口/不规则形状窗口的挺麻烦的.如果只要整体的半透明那SWT还好,如果要做不规则形状窗口,两者那是五十步笑百步了.

OK,作为本文前半段的小结,再回顾一下在这个学期之前,我主要使用的编程语言的顺序:
LOGO->(QBasic)->LC-2 Assembly->(C/C++)->Java
中间还有用过些别的语言,但没成什么气候:
JavaScript(这个用得最多), Python(次多),TVP JavaScript (TJS)(偶尔),OCAML(极少),i386 Assembly(逆向工程时经常看,很少写),Visual Basic(只在大一大二帮别人做作业时用过),Ruby & Lua(基本上没用过,只看过些资料)

这样的接触顺序与范围,对我对编程语言的认知有一定影响.
例如说,正是因为会有需要实际用JavaScript,所以会知道其实它也是一种面向对象编程语言,而不像某些所谓学者(嗯,例如说出什么面试宝典的)声称它不是OO的.只不过JavaScript(ECMAScript v1-3)采取了prototype-based的方式来实现OO,而C++,Java,C#等采用了class-based,这并不等于只有class-based才是OO机制的唯一实现方式.两种方式各有特点,并不能笼统的比较出个优劣.我们可以看到,class-based的OO设计模式里要实现一个Prototype模式要写多少东西,而同样的功能在prototype-based的OO语言中直接就是内建于语言中,使用成本自然不在一个级别上.
也是因为有用过JavaScript,Python,TJS与OCAML,我接触并了解到了closure(闭包)这个有趣概念.将自由变量与一个上下文绑定在一起的这种语言构造相当方便,在很多时候可以让代码更有条理.但用得不好也可能会有难以发现的糟糕后果,比如说写了糟糕JavaScript代码然后让IE崩溃之类.所以看到C# 2.0的匿名方法也支持闭包,又或是Java SE 7有可能加入闭包,我都没有惊讶,只是觉得这或许就是应有的趋势.

=======================================================================

C#

接下来,话题转到C#上.如本文开头提到的,我这个学期开始上.NET课之后才开始系统的学习C#.结果我现在觉得我已经离不开它.可能有人会觉得说,C#与Java在语言上不是一回事么,语法和构思都很像,哪里能算得上"大改变"呢? 实际使用经验告诉我,这两者在外表上的相似使在两者间迁移/转换的成本相当低,而背后提供的支持其实颇为不同,可以根据自己的需求去选用合适的.
我目前会碰到的一些使用场景,已经没有什么大的工程了,反正短期内肯定不会再参加像之前在学院做的那个项目那样规模的(换句话说,用不上Java EE).简单实用的语言就是我所希望能使用到的.对我来说,C#最能吸引我的地方,在于它在语言级提供的丰富功能.没错,其中有大量syntactic sugar,但那又有什么关系,好用就行.另外一点,使用C#来创建Windows上的GUI程序无疑非常方便,而且能保证look and feel是绝对的native;即便要跨平台使用自己写的GUI程序,Mono也能对System.Windows.Forms提供相当程度的支持.要说安全性,世界上没有绝对安全这么一说,要说ASP.NET不安全的话,JSP也有它自身的问题.

在我现在的使用场景中,之所以我会在C#与Java的选择中倾向前者,是因为:
1. 我需要做的使用,Java能做的C#都能做
2. 我的使用范围内,Mono几乎都能提供不错的支持,让我不用担心在linux上无法使用C#的问题
3. C#的语法虽然比Java复杂一些,但提供的功能都相当实用.至少,内建的property,indexer,delegate,event,这些都是我在用Java时很期待却得不到的;匿名方法又比Java的匿名内部类语法更简洁而且支持真正的闭包;内建的#region指令能更规范的整理代码;partial class机制很好的将自动生成的代码与手写的代码分离
4. C#的运行速度经常比Java快,至少在Windows上绝对比Java快
5. CLI比JVM更完善,更先进
6. 即使是ASP.NET,Mono也有XSP来提供支持,不用担心被锁在Windows上.将来也会对Silverlight提供支持(Moonlight计划)

就挑些我比较关注的地方来举例说说吧.上面的几点中1,2是基础,达不到的话我或许还是会出于惯性而不愿意转换语言;3是最重要的地方;4,5是附加得分点;6是我暂时用不到的地方.最近写的所有C#程序,包括作业和一些别的小程序,我都同时部署到.NET Framework 3.5 Beta 2与Mono 1.2.5.1上来观察效果,觉得Mono的完成度比我原本想象的要高,像System.Windows.Forms这种看起来平台相关性特别重的部分都实现得七七八八.我写的几个GUI程序,在Windows XP上用Microsoft .NET Framework 3.5 Beta 2的C#编译器编译后,直接放在openSUSE 10.2上跑也能(近乎)正确的跑起来,界面的显示,程序的功能什么都基本正常.这让我挺欣慰的,不用担心选择C#恒等于选择Microsoft/Windows.
写这篇blog时正好与超哥聊了下,交换了些意见.确实,就现状来说要完全发挥出C#的能力还是得在Windows平台上,用Microsoft的.NET Framework,但我相信Mono能够越来越完善,被越来越多的商业project所接受.不能小看了开源社区的力量,特别是当它有大公司出资出人来支持时.Mono计划从Microsoft得到的支持也不少,例如Moonlight小组使用的测试套件就是Microsoft那边直接给来的.做过TDD的人都会知道,一个测试套件意味着什么.

在Java中,要使用Observer模式很可能是很痛苦的.如果按照标准库里提供的机制,那么实现一个Observer模式需要用到java.util.Observable类与java.util.Observer接口.让被观察的类继承Observable类,让观察者实现Observer接口.这几乎能解决问题了,还差那么一点...就那么一点就要命了.差的地方,就在Java的OO是单根继承的,如果被观察者位于某个继承体系中(但不直接继承java.lang.Object)那就用不了java.util里提供的机制了.变通的办法有好几种,一种是改变自己程序中原本的继承体系,把被观察者的基类改为一个接口,让出基类的位置;还有一种就是抛弃Java标准库提供的机制,手工实现相关的功能,包括添加/删除观察者,维持观察者列表等.痛苦.
实际应用Observer模式一般是有背景的;一般是与"事件"相关的处理上,会很容易想到用Observer模式.上面可以看到用java.util包里的机制去实现Observer模式有限制,Java在"事件"问题上提供了另外一组设施:java.util.EventObject类与java.util.EventListener标记接口.
a. 当做参数传递的事件信息对象要继承EventObject类,
b. 而要作为监听器使用的类,一般是实现一个特化的接口,这个接口继承自EventListener标记接口并声明事件用到的方法名.
c. 最后,要发送事件的类必须自己实现一个监听器列表.
d. 用addXXXListener()方法将事件监听器(一个类)加入监听器列表中.
一个简单的例子: 参考 http://www.blogjava.net/chenweicai/archive/2007/04/13/110350.html

Java中实现Observer模式或者使用事件的痛苦,到C#里就轻了很多.C#内建了event关键字用于声明事件,实际上是一个syntactic sugar,同样会生成一个处理事件的类(包括其中的监听器列表的管理等).于是上面描述的Java中的几步,就变成:
a. 需要特化的事件参数时,继承System.EventArgs类.
b. 不需要.
c. 用event关键字,以System.EventHandler<T>为类型声明一个事件.其中T是具体的EventArgs类.
d. 用+=操作符将事件监听器(一个方法)作为参数传给具体的EventHandler就行.
在最简单的情况下,这样就完成了C#中事件的声明.当然还需要具体实现产生,发送事件的方法等,不过这些在C#与Java中都差不多所以省略了没说.很明显,一般情况下在C#中使用事件机智比在Java中要方便很多.然而C#也留给用户自己编写EventHandler的机会,可以自定义处理监听器(内建的机制无法满足性能需求时).

有时候我们并不真的需要一个"事件",而只是需要一个回调方法(callback method).Observer模式的存在可以说是过程式编程语言中callback function的一个生硬的实现.在C#中便不必痛苦,可以直接使用"delegate".以delegate关键字声明一个类型安全的"函数指针",在这里就叫一个delegate;然后直接就拿这delegate类型与具体的方法名(静态/成员)或匿名方法结合使用就行.Java中没有任何对应的机制,要做出callback的效果,多半涉及新创建一个接口(相当于C#的声明delegate类型)并实现它,然后在需要callback的地方以接口为类型来传递参数.

举这两个例子,主要想说明的是为什么我觉得第三点很重要,为什么出于这个原因我宁可接受语法比较复杂的C#.这是因为,我在使用别的语言(Java)时遇到了一些常见问题,但语言本身没有提供任何通用且简洁的机制去解决问题,迫使程序员接受相对繁琐的解决方式,or let's say "workarounds";假如这时我碰到了另外一种语言,它与我原本使用的语言十分相似(迁移成本低),而且正好有非常简洁的方法解决我原本遇到的问题(迁移价值高),那我没有什么理由继续抱着原来的语言不放而拒绝新语言.

但...there's always the "but".对我来说,由于我在学习C#之前已经学过了很多语言,对我来说delegate,event等关键字带来的是一种清爽感,让我觉得代码可以得到期待已久的简化.但假如有一个没接触过编程,或者是只有很浅的编程经验(例如只用QBasic写过很小的程序之类)的人来从头学习C#,我觉得C#很多内建功能反而会把人家吓跑.
这就是许多反对C#的人会提到的"kitchen sink".他们的观点是语言就是要保持本身的纯洁,简单,不应该看什么好就加什么,最后弄得像kitchen sink一样乱七八糟.这观点固然不错,历史也见证了许多事物从开始的简单,到中期的完善,到后期因功能过度膨胀而被新生的更简洁的事物取代的过程.不过这观点也给人一丝"洁癖"的感觉.我开发经验尚浅,没什么立场去评价这样的功能膨胀到底是好是坏;至少,C#还是很合我胃口.许多反对向Java SE 5之后的语言核心加入任何新功能的人之中,有不少是主张应该把精力集中在完善标准库上而不是增加语言特性去增加程序员的学习成本.嘛,众口总是难调,反正C#是先走了一步,Java要不要"Me Too"就等着瞧吧.

=======================================================================

D

我大概是一年多前看到"D Programming Language"这名词的.当时是在收集游戏引擎的资料,找到了一个叫Yaneurao系列的引擎,同时在作者的介绍页上看了个「D言語研究室」的链接,好奇点进去,看到了D语言的相关介绍.可惜那时没仔细看,只是随便瞄了眼,看到作者最新的更新也只到2004年;提到了C#与Java,然后看到范例代码与这两种语言极其相似;看到有reflection与GC...D语言就这样在我脑海里留下了错误的印象:
1. 我没看D语言的创立者是谁,还以为是Yaneurao系列游戏引擎的作者无聊造出的语言(因为造出样子很像C#/Java的语言的日本人有前例,嗯我说的是TJS)
2. 接上前一点,我以为它不过是"又一个Java"
3. 看到RTTI与GC,我将D语言默认为是以VM为基础的语言

其中,第一点,我过了好一段时间后凑巧发觉D语言的创立者是Walter Bright,还好.但余下的两点误解则一直带到了最近.毕竟,如果不过是"又一个Java"的话,我已经学习并使用TJS,旁边还有完成度很高的C#,何必花费精力去学又一个相似的东西呢.也不知道为什么,到今年国庆前的几天,我突然觉得兴头起来了,开始仔细了解D语言的实际情况.然后我惊讶了.实际状况跟我一直以来的印象完全不同:
1. D语言由Walter Bright创立,由Digital Mars开发并维护
2. 有两种主要的编译器,Digital Mars提供的DMD与GNU的GDC
3. 提供自动内存管理与GC,并在语言级提供了相关支持(默认的new运算符会将对象分配在托管堆上)
4. 提供比C++更完善的RTTI
5. 许多语言构造都有内建属性,例如数组有length属性
6. 有函数指针,有delegate;有限度支持闭包
7. 支持可变长度参数
8. 支持函数重载
9. 支持数组切片
10. 支持泛型编程
11. 支持契约编程
12. 支持mixin
13. 有scope guard,包括scope(exit), scope(success), scope(failure)
13. 语言级支持RAII(Resource Acquisition Is Initialization)
14. 不使用VM,代码是直接编译为native code的(!!)
15. 支持C语言式的调用方式,可以直接调用C语言写的库并与其相连接
// ......关于更多的D语言特性,请参考官网介绍
简单几句话根本列举不完D语言的特性.不过,要是让我用一句话概括我对D语言的认识的话,那就是"另一种C++的进化",删除小部分不需要的功能,调整了一些语义的默认行为,并大幅增强了其它部分的又一种编程语言.由于它像C++一样支持多种编程范型,只把它称为"面向对象编程语言"总觉得委屈它了.这么多的语言特性当然也伴随着很高的学习成本.我自认为我算是知道一点C,知道一点C++,熟悉Java,正不断熟悉C#,但即使是这样我在读D Language Specification时还是犯晕乎——语言内建的东西实在太多了!!

而经过两个星期的"挣扎",我确实的感到了Digital Mars的D语言主页上放在头条的那句评论的含义:
"It seems to me that most of the "new" programming languages fall into one of two categories: Those from academia with radical new paradigms and those from large corporations with a focus on RAD and the web. Maybe it's time for a new language born out of practical experience implementing compilers." -- Michael

D语言不是专供学术研究的那种先进但未必成熟的语言,也不是把关注点集中在RAD和web开发的大公司创造出来的商业味浓厚的语言.它只是根据实际需要而诞生的又一种语言,相对高效且实用.这里"实用"一词要表达的意思或许跟它的本意不太一样,我想说的是...如果现在有什么还活着的编程语言能被称为"kitchen sink"的话,那么在D的面前,Java根本排不上号,C#只能望洋兴叹,C++也无法与之想匹敌.D语言是名副其实的大杂烩,什么特性好用又只会有限度影响效率就会给加进来.
主说,要GC,不要麻烦,所以D语言内建了GC作为默认的内存管理方式
主说,要效率,不要VM,所以D语言把所有代码都编译到native code
主说,要泛型,不要重复代码,所以D语言提供了改进自C++的泛型语法,提供了独特的mixin特性
主说,要精简,不要膨胀的目标代码,所以D语言可以在编译时运行部分程序计算出结果
主说,要文档,不要混乱,所以D语言有独特的内嵌代码形式
...
...and you've got a kitchen sink.

该如何看待这个大杂烩(kitchen sink)呢? 学术上看这种方案显然不是简洁优雅的,因而academia guys大概不会支持它.初学者会看到一大堆语言特性而感到眼花缭乱,从一开始就对它感到畏惧.大公司在短时间内未必会接纳这个尚未成熟的方案.强调实干,敢于尝新的人或许会很高兴看到这么一种特性丰富的语言,并乐于从C++等语言转到D上继续编写软件.
我自己觉得并不会太在意语言提供非常丰富的语言特性,事实上我十分喜欢D语言,觉得这跟我理想中的使用语言很接近,以后写不带GUI的小程序时或许会首选D语言.但我会比较希望语言规范或资料中能将这巨大的语言特性列表归个类,将其划分到不同的aspect上,让熟悉不同编程范型的人能马上掌握D语言提供的相关机制,而不必在刚接触的时候受到自己用不到的特性的干扰.如果能做到这一点,我也可以向别的D语言初学者建议只从单一的编程范型(例如说基本的OOP)着手去认识D语言,避免混乱.在初步熟悉了一个方面的D语言之后,再慢慢了解其它的方面,灵活运用D语言丰富的内建特性来简化和加强自己的代码.
一种语言提供太多内建特性的一个坏处,就是大家虽然使用的是同一种语言,只使用核心语言的其中一个方面的人可能完全无法理解只使用另一方面的代码.这就是为什么很多时候一些语言的设计决定会是精简核心语言,而将多数的额外功能做到库里.D语言现在的设计决定显然有自己的道理,但对其的态度,还是见仁见智吧.

D语言是演化自C++的又一种语言,但它与Java/C#的方式有着绝大的不同.不采用VM不是单纯为了速度性能而做的设计考虑,而是在更高的层次——语言用途目标上的考虑.D适合于编写系统软件,在source-level能保证对多目标平台的支持就足够了,并且性能是重要的因素;Java/C#适合于编写能够直接部署在多种(异质)目标平台的应用程序,必须在各个目标平台上保证binary-level的兼容性,并且安全是重要的因素.避免使用VM,把源代码直接编译为native code,D能提供Java/C#达不到的性能指标,而且能保持source-level跨平台的能力,使其十分适合于编写performance-critical的系统软件.通过使用VM,Java/C#能提供D的程序无法轻易得到的安全性,并且可以在binary-level上跨越平台,简化了部署过程,使其十分适合于网络相关的开发.D与Java/C#在主要应用场景上其实并不冲突,也不太可能出现相互替代的状况.如果只是个一般的小程序,那累死VB之类的语言也都能行,也并不一定要在D与Java/C#之间作决断了.

大杂烩般的D语言为了混合一些无法兼容的特性而作出了不少奇怪的设计.下面只是其中很少的几个例子:

delete运算符:
熟悉C++的人应该不会对delete运算符感到陌生.这是C++中释放资源并/或使对象析构的重要手段.而熟悉Java与C#等内建了GC的人也会知道,这些语言一般会明确说明"没有确定性GC";也不提供delete运算符,因为没有意义.但在D这个大杂烩中,可以看到既有GC,又有能达到确定性GC效果的delete运算符.

Walter Bright对delete的解释是:(原文无修改)
"Deleting a pointer (or reference) will automatically null out the pointer (or reference). However, if you still have a dangling pointer to it elsewhere, that pointer will now point to garbage. I don't know how to eliminate this problem. This means that operatore delete is more of an optimization thing than a core feature."
按照Walter的意思,delete只是个"optimization"作用的关键字的话,那给人的感觉确实像一些人提到的,与C中的register关键字很像;但C中的register并不保证变量被分配到寄存器上,D中的delete却保证其操作数被GC掉,留下损坏数据的可能性.同时拥有指针,引用,GC与delete运算符不得不说是个十分糟糕的设计...

private修饰符:
在Java推广之初,很多C++背景的程序员都对Java中的protected感到困惑.现在,C++/Java/C#程序员肯定会对D语言中的private感到困惑.
"Private means that only members of the enclosing class can access the member, or members and functions in the same module as the enclosing class."
所以,D中的private基本上就是C++中的friend的变种一样."真正"的private跑哪里去了呢?

闭包:
在D语言中,可以定义嵌套的函数,并且内部函数可以使用外部包围(enclosing)函数的参数和局部变量.同时,静态的内部函数可以由function pointer指向,非静态内部函数可以由delegate指向.当这个function pointer或delegate被用在别的作用域中时,外部闭包函数就对内部函数形成了一个闭包,使原本是自由变量的参数和局部变量绑定到内部函数上.这看起来跟一般的闭包没什么区别,但是...
但是,D语言所支持的闭包是"动态闭包"(dynamic closure)而不是一般认识中的闭包.指向内部函数的function pointer或delegate,只能在外部包围函数的栈框架有效时使用.换句话说,不可以把一个内部函数作为delegate类型的返回值返回到"外面"然后再调用.

举例来说,下面的D代码是正确的:
Java代码
 
  1. struct Foo   
  2. {   
  3.     int a = 7;   
  4.     int bar() { return a; }   
  5. }   
  6.   
  7. int foo(int delegate() dg)   
  8. {   
  9.     return dg() + 1;   
  10. }   
  11.   
  12. void test()   
  13. {   
  14.     int x = 27;   
  15.     int abc() { return x; }   
  16.     Foo f;   
  17.     int i;   
  18.   
  19.     i = foo(&abc);      // i is set to 28   
  20.     i = foo(&f.bar);    // i is set to 8   
  21. }  

这段代码显示了"动态闭包"的应用,以及使用一个内部函数与一个成员方法作为delegate类型的参数的行为等价性.

但是下面的代码却不可行:
Java代码
 
  1. /* NOTE NOTE NOTE  
  2.  *  
  3.  * THIS CODE IS BROKEN.  This is an example of something that you  
  4.  * should NOT do!  
  5.  *  
  6.  * You can never return a stack delegate because the data pointer of that  
  7.  * delegate points to the stack frame where the delegate was created.  Once  
  8.  * you return from the function, that stack frame goes away and the memory  
  9.  * is reused by some other function.  
  10.  *  
  11.  * Most likely, when you call this stack delegate, your program won't crash  
  12.  * (since the pointer points to a valid address in the stack), but you will be  
  13.  * reading trash values, since the memory has been reused by some other  
  14.  * function.  
  15.  *  
  16.  * Of course, if one of the variables is a pointer, then you would crash when  
  17.  * you read & follow the pointer that is no longer valid.  
  18.  */  
  19.   
  20. import std.stdio;   
  21.   
  22. int delegate() foo() {   
  23.     int a = 1;   
  24.     int b = 2;   
  25.   
  26.     writefln("int delegate() foo() is called. Locals a = %d, b = %d", a, b);   
  27.     writefln("BUG!  You must NEVER return a stack delegate!");   
  28.   
  29.     return delegate int() { return a+b; };   
  30. }   
  31.   
  32.   
  33. int main() {    
  34.     foo();   
  35.     return 0;   
  36. }  

如果把main()中对foo()的调用的返回结果用一个delegate接着,并且调用那个delegate的话,则行为是未定义的,因为局部变量a与b已经不存在了.

美其名曰"动态闭包",用惯了JavaScript或者C#等支持完全闭包语言的人肯定难以接受D里的这种半调子的闭包...但这是Walter Bright的一个设计决定,为了性能考虑,他决定不采用需要在堆上分配空间的完整闭包语义,而只采用在栈上分配空间(也就是,相对普通的函数调用栈没有区别)的"动态闭包".
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
【原创】JAVA和C#,武当和少林之争!
Ajax: Java BluePrints 和 Rails对它的封装
为什么C++语言中既有指针也有引用
使用Go语言工作400天后的感受
王垠 One语言
function/bind的救赎(上)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服