各语言设计思想的独特之处:C/C++、Java、Python、Objective C
说明:本文章纯属个人观点,不保证绝对正确,欢迎大家批评和指正,同时我自己也会对本文不断的更新和完善。
本人学过多种语言,有的是工作需要,有的则是因兴趣自学,我学习语言目的不完全是为了使用它开发项目,也不是为了装逼,主要是学习各个语言的设计思想,虽说天下语言皆出自Lisp和Smalltalk(此句摘自高级程序员装逼指南,这个是搞笑的文章,笑笑就行,别当真),但经过多年的分化改进,它们都有了自己独有的东西,有自己独特的设计思想和智慧,我是学的是它为何要这样设计,有什么好处,学会这些思想和技巧有助力我自己设计软件架构,如果把语言本身当成武功的形法,那他的设计思想就可以当成武功的心法,能提升自己的架构设计能力,当然,不用担心我练的心法太多而走火入魔,因为我不会使它的100%,只抽取好的思想。
C和C++
人人都说学C如果不学指针等于没学(悲剧的是我在校主修数学专业的时候,老师就没教我们指针,理由是认为说我们听不懂),这句话一点都不夸张,C确是因为指针流行的,所以说C和C++最大的特点莫过于指针,指针是什么,估计大部分人都知道,但考虑到没过C的和我以前数学班的童鞋,我简略介绍一下,所谓的指针就是...,唉不好描述,还是先画个图吧:
指针看到了吧(不要钻牛角尖说针没这么大,是箭头),所谓的指针就是一个指向内存的地址,计算机本质就是读存储器和写存储器(与外设交互设备也是往一个寄存器地址读写内容),假如你都可以直接向任何地址读写了,你说你还有什么不能做,所以就造就了C的强大,操作系统和驱动都可以用C编写。C++就是C的超集,完美兼容C,可以把C++当成C的升级版,英文翻译是这样的C Plus Plus,注意世上没有C Plus,另外千万别认为C#是C++的升级版,他们俩没有亲戚关系的,C#的英文翻译是C Sharp而不是C Plus Plus Plus Plus(这样读会累死人),C++是贝尔实验室出来的,而C#是微软做的东东,所有C#也只能用于Windows开发,你可能已经发现以前Windows开发主要用VC++,而现在的工作重心慢慢转移到C#,牛B公司都想搞自己的语言,Apple公司有自己Objective C,不过Objective C与C++相差更是十万八千里,等下详述。回到正题,C++主要是在C的基础上增加了面向对象支持,C++的面向对象直接多继承和虚继承,多继承就是一个类可以继承多个类,虚继承是为了防止多重继承产生的二义性问题,具体还是问谷歌吧,一言难尽哪。另外,C++还有类指针是其它语言没有的,在Objective C是用selector代替,Python、Java中是用引用,不过C++也有引用。其它还有泛型是C没有的,Java也有,Python、Objective C等动态语言不需要泛型。还有C++的类方法不是默认是多态的,要加一个virtual关键字话,是因为C++的多态是用虚表实现的,虚表是一个以指针为元素表,通过改变指针来实现动态绑定,Java所有的函数都默认支持多态,Python和Objective更加恐怖,前一个是有个__dict__表记录了所有方法,你调用的时候,就去找个那个方法就行,后一个所有的方法调用都叫做给类发消息,方法不存在都不会挂,等下详述。
Java
Java是我学的第二门语言,用的次数不少于C++,我在其中学过很多思想,学这门语言使得我后来学习其它语言感觉很容易,同时也使我对C++的理解更深刻了。对我来说,Java最大的特点就是对面向对象的完美诠解,在学Java之前,我对面向对象的理解总是玄乎乎的,犹如水中月,镜中花,依惜可见却摸不着,够不到,不能充分理解,经常用面向过程的思维去写类,学了Java一两个月之后,我终于伸手抓住了真正的花。众所周知,Java中,一切皆类,main函数都要放到类中。
面象对象中最重要的特性包含继承与多态,继承很好理解,多态则不然,在学C++时,我知道了多态的实现机制却还没有理解多态,理解多态最好的工具就是接口,你可以把接口当成一个特殊的类,它不能有成员变量(除非静态成员变量),所有方法规定不能实现,而其实现类(抽象类除外)必须实现所有方法,回顾一下多态的概念,即同样的接口却有不同的实现,我刚毕业时总不能理解这句话,面试问的时候就背诵这句和那个三角形和正方形的例子,别人多问一句就问倒了,举个例子,输入流,用这个好理解一点,输入流的接口是InputStream,要使用输入流的人员只需知道InputStream是干嘛用的,有哪些功能即可,而不要关心他是什么流,网络流还是文件流或者模拟的Buffer流,更不用关心它是怎么实现的,对不了解流的人补充一句:输入流是一个抽象的概念,一般只能从前往后流,想流水一样,读过之后就能往回流了(也有特殊的)。比如说你要打印一个文件的二进制,如果文件在本地,就用文件流,如果哪一天把这个文件放到服务器上了,你就得用网络流了,但无论你引用的哪种流,你从流中读数据的代码是一样,代码如下:
void foo() { // 文件流 File file = new File(FILE_OUT_SUBDIR_NAME + "/test.xml"); FileInputStream fileInputStream = new FileInputStream(file); // 网络流 URL url = new URL("http://192.168.1.*/directory/to/file.txt"); URLConnection conn = url.openConnection(); InputStream netInputStream = conn.getInputStream(); printStream(fileInputStream); printStream(netInputStream); } void printStream(InputStream inputStream) { byte[] byte1 = new byte[1]; while (inputStream.read(byte1) != -1) { System.out.printf("%02X", byte1[0]); } }
从这种代码里你还能从new的时候看到具体实现类,如果前面一部分封装成一个函数,你就连是哪个类都看不到了,Java里有很多用工厂模式对创建对类进行了封装,如加密用的JCA,你传入一个算法的名称就会获取相应的类,但你只知道他的接口,看不出具体实现类。设计模式里更是充分的利用了多态,比如State模式,可以大大减少了if-else出现的次数,啰嗦一句,其实State模式是从另一个维度去看一个程序的逻辑,即把纵向逻辑改为横向逻辑,从而把公共的if-else提取出来,这个主题本文暂不详述,后续推出新文章专门讨论设计模式。接口在用界面时主要应用在事件监听器,其实作用就相当于C里的回调,所以反过来,意味着C也可以实现面向对象,最好的实例就是Linux内核,回调就是函数指针,再一次证明了指针的强大。
Java还强大的标准库,我实习的时候问一个C++老员工Java是什么样语言,他跟说Java很简单,就是很多包调来调去,做什么功能都有相应的包,我估计很多人初识者会这么认为,其实他们说的一堆包就是Java强大的标库,C++面试官司可能会问你懂不懂STL啊,知不知道什么是Vector和Set啊,Java面试官可不会问这个,因为这个对Java来说是必须的,最基础的,我想刚学Java就要用ArrayList吧,也有人因此说做C++好,什么要自己实现,所以学得多一点,Java什么都封装好,没什么学的。我不太认同这种观点,因为没必要重复造车轮,这些前人都写好,直接用就好了啊,你就可以有更多的时间想想你自己软件的如何设计,话说回来,如果是为了学习的话,其实这些我们在学校就做过练习写过(如果你在校没写过,也可以业余时间写写啊),只是写得没人家好,即便你再写一篇也未必写得有人家好,所以每个人都去写一篇,意义不好,还有一种情况,如果你实在想去写标准库,那就写好你现在的软件,练好基本功,先学习标准库中别人优秀作品,等将来羽翼丰满时,你会有大把机会写库给别人用,但最好要理解其设计思想,因为库的实现是很简单的,主要是定出合理的API很难。
Java还有一个牛B特性就是反射机制,它可以通过字符串来获取类的属性,包括方法和变量,这个机制使得Java的灵活性强大了不止一倍,不过凡事都有两面性, 在它带来灵活性的同时,也破坏了封装,不过还是要用,但不要轻易用,Spring的AOP都知道吧,强大吧,方便吧,我不用看它的代码也能百分之一百确定是用反射机制实现。
Python
python是我最熟悉的一门动态语言,它很好的诠释了什么叫动态,其实在接触python之前,我接触过的动态语言还有shell脚本语言和Objective C、但这两种还不能让我深刻理解什么叫动态,可能是它们不够动,python如此好的动态性以致于其它后来创造的语言都借鉴了它的实现,如groovy,他的创造者公开说了是因为python才使得他创造了groovy。python的动态性得益于它的一种设计思想:一切皆对象(Java是一切皆类,看来走极端也可以有出息,嗯~呵呵~)。这样说你可能还不能懂,再详细一点就是,python中,字符串,元组,就连类和函数都是对象,你说神奇不神奇。python的对象里都有一个__dict__属性,是字典类型,这种类型你可能没听过,不过你可以当成是Java或C++里的Map或是Shell里关联数组,记录这个对象所有属性,包括方法和属性,调用时,解析器只要搜索一下__dict__就可以了,更神奇的是你还可以通过赋值改变这个__dict__的内容来达到改变这个对象,比如添加字段,这样有点Java的反射机制吧,不过python更灵活,呵呵。所谓的动态,主要是指变量的动态,python中所有变量都没有类型,其实可以看成是一个引用或指针,每一个变量只是指向一个对象,而且它可以随时指向另一个对象,包括函数对象,这种灵活得益于python里对象的设计。
python的标准库也挺强大:意味着可以用很少python代码实现某个功能,而用其它语言则要用很长的代码,这也成就了python简洁的威名。我学python的导火线(我很早就打算要学python,但平时工作很忙,也没有导火线)就是我同事用了一点python代码写一个post JSON流到服务器的测试脚本,于是我恨下决心,第二天就是开始挤出时间学python,另外,我曾经用20行以下代码写过一个图片缩放的脚本,简单的无法想象,做为分享,我还是贴上来吧,还有一个发送邮件的脚本也一起贴了吧。
缩小图片的(你可能要下载PIL模块):
from PIL import Image import os import os.path import sys path = sys.argv[1] small_path = (path[:-1] if path[-1]=='/' else path) +'_small' print small_path if not os.path.exists(small_path): os.mkdir(small_path) for root, dirs, files in os.walk(path): for f in files: fp = os.path.join(root, f) img = Image.open(fp) w, h = img.size img.resize((w/4, h/4)).save(os.path.join(small_path, f), "JPEG") print fp
发送邮件的:
import smtplib username="[email protected]" recipient=username passwd="123456" host='smtp.gmail.com' port=587 body_of_email="body of email" print 'initializing...' session = smtplib.SMTP(host, port) session.ehlo() session.starttls() #session.ehlo() print 'login...' session.login(username, passwd) headers = "\r\n".join(["from: " + username, "subject: test", "to: " + recipient, "mime-version: 1.0", "content-type: text/html"]) # body_of_email can be plaintext or html! content = headers + "\r\n\r\n" + body_of_email # msg = "To:username@domain\r\nFrom: username@domain\r\nSubject: test \r\n\r\ntest mail\r\n" print 'send...' session.sendmail(username,username, content) print 'close...' session.close() exit()
Python有良好的扩展性,python每个文件都默认是一个模块,你只要import就行了,模块即可以单独运行还可以当行库,这是因为python解释器根据会向模块传一个参数。对Python来说,Java的JNI没什么稀奇的,python也可以支持调C/C++,跟Java更是可以无缝融合,所以很多服务器使用python和Java混合开发。
python有对函数式编程的也有支持,关于函数式编程另查资料,这里不详述,python支持加量化(Curring)和偏函数,这两者不是语言的机制,而是像“继承与多态”一像是一种概念,加量化是利用单参数的函数转换成多函数,偏函数是指固定函数的部分变量后再调用,这个“偏”字的含义跟高数里的偏微分差不多,都是指先固定一部分变量,这两者的区别具体可参考此文,它们的实现主要是通过闭包、lambda和functools模块来实现的。听说Java8也要加闭包特性,不过当下的Java可以的用匿名类实现类似的功能,就可以多写个接口。
Python的装饰符(Decorator):这也是个很好的功能,可以支持所谓的切面编程,像Spring一样,其它还有在python里,一个函数的执行,可以暂停,这个比较新鲜,一般人都没听过,也告诉我们平时想问题时要勇于打破常规,think outside the box,这个思想的实现是用了关键字yield,如果好奇就学学python吧。
python还有一点比较独特,就是它使缩进来表达语句逻辑,不允许使用大括号。如:
if True: print 'True'; else: print 'False' print ''hello"
输出是:
True
而没有“hello”,如果是这样:
if True: print 'True'; else: print 'False' print ''hello"
输出则是:
True hello
这个特点被官方文档和一些书藉吹得在花乱坠,引用python核心编程第3.1.4节的一段:“使用缩进对齐这种方式组织代码,不但代码风格优雅,而且也大大提高了代码的可读性。而且它有效的避免了"悬挂 else"(dangling-else)问题,和未写大括号的单一子句问题。(如果 C 语言中 if 语句没写大括号,而后面却跟着两个缩近的语句,这会造成不论条件表达式是否成立,第二个语句总会执行。这种问题很难调试,不知道困惑了多少程序员。)”,这段主要是说:一、缩进增加易读性;二、防止C那样的“悬挂else”问题。但我不认同他的观点,关于第一点,我认为无论什么语言,给代码加上良好的缩进是一个程序员都基本的职业素质,而且几乎所有人都能做到这点,所以python创建者白担心了;关于第二点,也是大部分人都听说过“悬挂else”问题,而且一般人都会习惯性给else语句加上大括号,这个问题便很难出现,反而python这种机制困惑了我不止一次,第一种麻烦是如果你无意间删除或增加了一个Tab,编译也会正常,只是运行的时候会很怪;第二种麻烦是有时候在网上copy一段代码,那个缩进很有可能不对,但是你检查不出;第三种麻烦,你copy了一段代码,看起来很正确,却编译不过,报的错是缩进不一致,原因是缩进很起来是一样,但有的是4个空格,有的是一个tab,这个时候你就得找一个能显示空格和Tab的编辑器,睁大眼睛一个一个找了。
Objective C
初学Objective C的时候,感觉它是语言界面的奇葩,最大特点就是中适号语法了,当人家的方法调用都是点号的时候他用的是中括号,不,它没有调用的说法,它是发消息,我当时就很惊讶,原来还可以这么搞啊,真是另辟蹊径啊,Objective C所有的方法“调用”都给对象发消息,对象收到消息可以不响应,还不会挂,也可以把消息发给空对象,这正是打破常规,这相当像乔帮主的风格(但不是他创建的,他只写了个库而己,里面的标识符都以NS开头)。说到空,还有一件奇怪事实,Objective C里有新加了两种空,nil和Nil,一个表示空对象一个空类。除此之外还有区别的是布尔型使用BOOL、YES和NO关键,SET表示一种类型类似于C++的类指针,protocol类型类似于Java的接口,还有一种叫catogory的,使用他可以动态修改和扩展类而不必使用继承。Objective C2.0有内存管理不过是编译器支持的,所以不会影响性能,不像Java有个垃圾回收器定期运行。另外Objective C很多关键以@开头,如申明和实现类分别用@interface和@implementation,也许你会有一个疑问,为何有这么多新出的关键,原因是因为Objective C也是C的超集,它为了不跟C的关键字冲突才新加了这么多奇怪的关键字,C++和Objective C都是C的超集,而且好像是同年出生的,但两者却有着本质,所以不要认为懂C++就好学Objective C,完全不是,它俩完全出于不同的语系,有不同的父亲,C++是Simula系,而Objective C是Smalltalk系的,C++是静态语言,Objective C是动态语言,它们有着完全不同的设计思想,但都有自己独特的智慧。网上有个外国人写了一个C++和Objective C的比较文档,不过是英文版的,我有时间会翻译一下放到本博客(blog.csdn.net/yanquan345)。这里又说明了C强大到可以创造一门新语言。
Objective C还有一个改进的地方,叫property,都知道面向对象的封装,所以Java建议用setter和getter去操作成员变量,如果是这样,那假如一个类设计为存储数据,当成C的结构体来用,那这个类可能得很多setter和getter,虽然Java的强大IDE可以自动生成代码,但你看这个类的时候会有点头大吧,在Objective C里就不用这样了,只要你用property修饰一下,说明那里成员变量可以被set和get就可以,顿时代码就简洁了许多,property其实也是自动生成setter和getter代码,但它是编译器生成,源文件是保持简洁的。
Groovy
前面也提到了的诞生离不开python,按照其创造者的说法,他是因为喜欢python有而Java中缺少的一些灵活的特性,才创造了groovy,这些特性主要包含动态性,对元数据的支持,元数据可以有童鞋不懂,我多说一句,元数据指的就是类的属性,因为一个类也有很多属性,如类名,成员列表等,这些都是元数据,所以python和Objective C都有元类,即类的类,普通则变成了元类的对象。所以groovy和python一样都很灵活,groovy最大的优势是可以直接在JVM中运行,可以不编译,也可以编译成.class文件,可想到做Java开发的时候可用groovy做一些简洁的事,我曾想让groovy做Android开发,但当时失败了,不过我失败,可能会有人成功实现这个想法,也有可能我以后会成功。groovy可能是大家听得最少的,我也是因为研究Google在IO大会发布的新Android开发IDE(基于IntellJ的)才接触到的。groovy的元数据有强大用处,就是很方便开发领域语言,领域语言我也要解释一下,我们说的C++和Java都是通用语言,就是可以做出任何产品,领域语言只能用在特定领域,一般有个解释器,其实一般都接触过领域语言,像Makefile,Ant、Maven脚本都是领域语言,你不能用Makefile脚本实现一个QQ吧,groovy对领域语言的支持最好的例子,就是gradle,一种新的自动化构建工具,号称既有Ant的灵活,也有Maven的强大依赖管理和标准规范。还有个论据是Groovy的强大的元数据使用Java的反射机制和Spring的AOP成了浮云,在Groovy里,你一不小心就用上了AOP,Groovy语言简单易学,它完全兼容Java语法,Java代码可直接运行,但它可以更简洁,特别相对学过python的童鞋更容易学,官网有完备的文档,还算是易懂的英文,因为词汇量比较少,建议大家去看,一定会惊讶你。
后言:Lisp不打算讨论了,过时了,讨论也没什么意义了,我自己都差不多忘了。另外,暂不讨论JavaScript,一是因为其特殊之后少,二是本人还不很熟悉,也许后续会加上去,如果有很多童鞋希望讨论,那我就豁出吃饭时间也定会加上。
参考资料:
Effective Java, 2nd, Joshua Bloch
Java Cryptography Architecture (JCA)
GoF Design Pattern
Python核心编程,第二版,Wesley.J.Chun,英文版是:Core Python Programming, 2nd, Wesley.J.Chun
From C++ To Objective-Cversion 2.1, Pierre Chatelier
Programming in Objective-C, 4th Edition
Groovy Guide
说明:本文章纯属个人观点,不保证绝对正确,欢迎大家批评和指正,同时我自己也会对本文不断的更新和完善。