一,前言
网上经常看到编程语言之争,大伙儿皈依到不同门派,各自怀抱信仰,时不时还发生点儿“冲突”。
这其中,C++和Java的优劣,十多年前就常吵的火热。然而时代在进步,技术在发展,满街早已是“云大智”了,现在还讨论C++ vs Java这个话题,似乎早已经过时了。

家鸽资质鲁钝学的慢,热门的知识没掌握,只能拿这个老话题炒炒冷饭。
参加工作的前两年,主要写Java;近些年由于工作需要,主要写C/C++了。也算是敝帚自珍吧,常觉得之前学会儿点儿东西不容易,舍不得丢掉。总会自觉不自觉的拿现在用的相互对比,也就粗略的总结了些东西,又怕记性不好以后忘了,就写个文字形式的记下来。
这个系列不敢说两种语言孰优孰劣,只妄图达成一个具体的目标:认真看过这些文字的人,以前写主写C/C++的,也能够写Java了;以前主做Java的,也可以尝试做C/C++了。这当然仅限于语言本身这个层面,框架与应用领域等范畴不在此列。与此同时,要是家鸽能够略微传达出一些观念,那就更好了。
二,形而上形而上谓之道。人在道中而不知道,如同鱼在水中却不知水一样。
语言层面的“道”,是它的设计理念与发展思路。先看一下C++。它就是一个实用主义的大杂烩,有点儿吸星大法的感觉。
Java的语言特性相当精炼,容易掌握,以至于很大一部分Java程序员相对的不怎么讨论语言特性本身,能更多的面向实际问题,将主要精力放在各种框架,API,设计模式中。你当然可以说,C++项目那些问题是由于编程人员水平不够,而不是语言本身的问题。但在Java这一套新的语言体系中,更多的人经过短期学习,更容易高效的写出问题很少的代码,何乐而不为呢?
高度抽象,是Java的设计原则。Java本身就是抽象而完备的;并且基于它可以进行更高层面的抽象。这使得代码量少,维护性好,研发工作高效,安全性容易得到保证。纯粹的面向对象是Java语言核心的思想与原则,在Java的王国里“一切都是对象”,连基本类型都有对应的包装类。它的复用也是更多基于面向对象来实现的。更完备的抽象,完全的面向对象,意味着更完全的封装,程序员不再需要知道细节就可以完成任务,也可以把无关他人的细节更好的隐藏起来。当然在一些情况下,掌控底层成为一种必要,它就无能为力了。在Java的设计思想中,开发效率的重要性高于程序运行效率本身,这在计算资源不是主要约束的实现需求中,显然有很大优势。
质量与安全性也是重要的考虑,除了规避掉容易引入问题的语言特性,Java风格中的异常处理机制非常完善。很多时候同一段逻辑,Java的写法也相对单一,不够自如灵活,但安全性与质量更高。在自由与安全中,Java选择了安全。
Java的语言特性相对精炼,但它也有C++不具备的语言特性。这些语言特性或者更加面向对象,比如接口。或者使它更能适用于Web,并发(C++也引入了)等领域当中。本节的描述有些抽象,不易理解,至少不容易有切身体会。为了说明问题,后文中家鸽会用自己写的一些代码示例,C++代码用黑背景,Java用浅绿。这些示例仅为突出演示某些语言特性的“玩具例子”,尽可能的简短易懂,但并不适用于实际工程。三、 形而下
形而上谓之道,形而下谓之器。舍器而近道者,几稀!
器,可以简单的理解为工具。编程语言本来就是工具,工具的区别在于使用方法。上图中,C++,C,Pascal都类似瑞士军刀,是用来做细活的工具。C语言的刀上有个USB,说明是可以做硬件操作的。C++刀是什么都有,说明C++是一种特性(过分)繁多的语言。Python是把电锯,面对大型的物体的修整,威力很大,人挡杀人,佛招杀佛,比C++/C/Java什么的得心应手得多得多,但是相对并不适合一些精细的调优工作。Java/C#是一把单刃工具刀,相对来说,其语法和使用相并不复杂。
不太关注技术细节的朋友,这一节后面的文字可以略过,直接看下一节。
这一节从语言具体的用法出发,来分析一下C++与Java二者的区别。可以粗分为三类:
Java不支持的C++特性
C++不支持的Java特性
C++和Java都有但是却不相同的特性
限于篇幅,本文只阐述前两种情况,第三种情况后续系列中再行阐述。
Java不支持的C++的特性Java不支持指针;
Java不支持预处理,也不再支持预处理指令(宏等等都没有了);
Java不支持typedef;
Java不支持goto语句;
Java不支持结构体与联合体;
Java不支持操作符重载(比如Java中的<>不再重载I/O操作);
Java不支持全局变量或全局函数;
Java不支持默认参数。
Java不支持析构函数。(Java增加了finalize()函数);
Java不支持delete操作符;
Java中,参数传递的方式只能是传值(“传值”这个说法有争议,且看后文详细解释。C++中可以传值、传指针或传引用);
Java不支持多重继承,即不允许一个子类继承多个父类。
............
下面挑几条典型的详细分析一下。
指针
最显然的区别是,C++支持指针,而Java不支持。
下面这个例子,将平面一个点,移动到与它原点对称的位置。
上面例子的写法都有些笨拙。比如可以用STL的copy()函数也许会清爽一些。这里只为比较"指针"这一点。
可以看到,C++有了指针,灵活的多也自由的多,在细节操作方面有更多掌控力;但代码也复杂了。比如*p++这种写法,不熟悉的话,总会纠结优先级结合方式之类。指针操作不当也有相当的风险,加之可读性下降,一般说来开发效率是有所降低的。
Java在设计之初,就把安全性与开发效率作为考虑的重要因素。在语法上去除指针,从根本上就避免了一些可能的质量问题。而且语法相对简单,容易上手,不用太过小心翼翼规避问题,开发效率相对也会高些。
对象的生命周期
标号为(1)的C++构造函数调用中,我们可以把u本身当作一个User对象。实际是在栈空间中构造的对象。这种构造方法Java中并不支持(基本类型除外)。标号为(2)的C++构造函数调用中,使用一个指针p指向堆空间中构造的对象。类似Java中引用q的作用。
参数传递


家鸽喜欢的所谓“Java一切皆传值”的说法,对于基本数据类型,指的便是变量值的拷贝,而对于对象(或String、Integer等包装基本类型),指的是对象地址的拷贝,可以理解为所传递的值是对象的地址。
C++的参数传递有传值,传引用,传指针的用法,还可以传多重指针,搭配上const和参数的析构问题,相对复杂很多;C++程序员始终面临着什么情况下什么样的用法比较好这个问题,大神可以写出简明易用的代码,令人叹为观止,而大部分程序员工作多年也搞不清楚,导致项目代码混乱不堪,惨不忍睹。这些在Java程序员眼中都不是问题,没有引用指针等方式反而简单,用JavaBean之类的包装类传参轻松愉快不出问题,中规中矩,虽然有时候包装类太多也会显得不够简洁。
其他
C++不支持的Java特性
包
上面这个程序中定义了一个名为Module的名字空间,为了在main函数中使用名字空间中所定义的方法,使用using指令。
Java有现成的“包”,而C++程序的模块组织结构,更多需要自己设计与实现。不同项目的结构不一定相同。这在带来自由度的同时,也变的不及Java方便。接口
C++中的抽象类,是通过纯虚函数实现的,读起来相对不那么明确。而且有不为人知的用法,比如与大部分人的理解不同,虽然抽象类不可实例化,但纯虚函数其实是可以有对应的实现的,并且还有方法调用纯虚函数的实现(不要惊讶,家鸽这里没写错),而且这种写法也是有适用场合的。限于篇幅,这里不做深入描述。后续会专写一篇探究虚表的文章。这里只是举例,看一下C++语法的隐晦与诡秘。
一旦类中包含了abstract方法,那类该类必须声明为abstract类;
抽象类不一定有抽象方法,有抽象方法的类一定是抽象类或者是接口;
抽象类不能实例化,只能被继承;
抽象类的子类,要么是抽象类,要么重写抽象类中的所有抽象方法。
几句话说明白,是不是很清楚,而且也藏不了什么特殊用法。二者的不同风格在这个例子中也有所体现。
另一个相关概念是继承。C++支持多重继承,因而有了菱形继承的问题和虚继承等等概念。这个用法很灵活,也有适用场合。比如需要适配的场景,可以用适配器模式解决问题。它分为两种,类适配器和对象适配器,类适配器用多重继承的方式比较合适。
四、多与少
少即是多
前几日的国庆阅兵,我们看到了陆军,海军,空军,火箭军,也看到了雷军。中国互联网史的元老人物雷军,在改革开放民营企业家的彩车方阵中,站在花车的C位出席了庆典。
雷军曾看到,传统的软件研发项目过于复杂,干着干着就干偏了方向,所以一定要把事情简化到两、三个月就能做完,而且,这两、三个月做完可能就解决了某一些用户的最核心的需求,然后这件事情就可以干了。
专注,是很重要的一件事情,是每天需要不断地提醒自己的事情。“少就是多”,通常情况下,时间少,资源少,人也很不够,只能尽可能少做事情,找到关键点,以少胜多。用手机举例,在诺基亚和MOTO的时代,一个手机厂商一年要出60款,甚至100款手机,手机的型号都是很复杂的,没有人记得住哪个产品的名字。
但从苹果开始,产品型号简单的要死,每个产品型号都特别的简单,但是大家都记住了他的产品型号。“集中所有的精力做一款产品”的成功概率,肯定比“分散精力做100款产品”要高,所以,要集中所有的资源,认认真真做好一两款产品,这就是“专注”。
于是雷军认为"砍掉90%,只做10%,认认真真把那10%做好就足够好"。通常的产品经理,是这个也想做,那个也想做,最后看起来花里胡哨,实际什么都没做好。优秀的产品经理,不是把需求往多了搞,而是不断的砍“需求”,专注于最核心的事情。早期的建筑大师路德维希·密斯·凡德罗就提出:“Less is more。”这也是他坚持的哲学,他的设计作品中各个细部精简到不可精简的绝对境界,不少作品结构几乎完全暴露,但是它们高贵、雅致,已使结构本身升华为建筑艺术。
老外说的的"architecture"来源于建筑领域。很多优秀的产品,都是简洁的代表。一款好的软件,必然是在满足需求的大前提下,拥有高适应能力和低复杂度的产品。如何控制复杂度就是软件工程的一个核心问题,不论业务架构、功能架构还是技术架构。所谓”复杂“不是“深”,而是“多”。通常的软件常常带着太过繁多的功能项目,没有分类整理,有着太过冗杂的交互。设计人员似乎为了彰显功能的强大,恨不能让屏幕上满是按钮,找个东西很难,仔细分析才发现未必有什么特别厉害的功能。技术实现中需要一次性考虑太多的对象,对象和对象之前的关系也繁多混杂,两两间都可能存在着关联关系,如同一个满是跳线的电路板,维护修改越来越困难,质量也越来越难以保证,这样显然做出不好软件。反观Google的大神们,做的页面只有一个logo和一个搜索框,却可以在十几毫秒内中从全世界包罗万象的海量web信息中获取到相应主题的内容,并完成相关度计算排序等工作;其中的大数据存储、处理与检索技术高难艰深,架构设计却精巧清晰。是“少即是多”的典型代表。
通过上面“形而下”一节的分析,C++和Java的区别,语言层面上归结起来,C++是“多”,而Java是“少”。
C++的多,是有历史原因的。从他诞生那一刻起,它必须兼容C,又要面向对象(或者说基于对象)。随后的发展要求他支持泛型编程。为了保有市场占有率,确保已有领域的优势,它必须足够贴近底层;为了保持活力,又必须吸收现代语言的新特性;与之同时还要保证对历史代码的兼容......搞到后来,语言特性越来越多,理论上什么都可以干;而且一段逻辑,可以用多种写法或不同风格来实现。
什么都可以干,注定了它的强大灵活,作为技术人员,掌握了这种语言,如同手握利器,知识类比迁移起来也容易。但什么都可以干常常意味着什么都容易干不好。就项目而言,针对自己的需求与所属的领域,很多时候常常有更好的选择。
语言特性就越来越多,初学者的门槛也越来越高,很少有人敢说自己“精通”C++。这严重影响了它的使用与推广。

Java就是那个“少”。他的设计者吸取了C++语言在项目中遇到的问题,砍掉了容易引入出问题的语言特性,变的“少”起来;在语言层面,一些事情的做法也变的相对单一化了。让开发、维护与质量保证变得相对简单,研发效率大幅提升。虽然这样做也会损失一些性能,使之不再适合做一些领域的事情。但在它擅长的领域(网络,Web, 企业级应用等),它成为经久不衰的语言,并为后来者(如C#)所仿效。如果把语言当成一种产品,从受欢迎程度和使用率上来说,Java显然比C++要成功。不少C++程序员并没有多喜欢C++,而是项目类型和需求决定,必须得使用它。
通常3年的Java程序员,多在讨论各种框架,API,设计模式乃至架构思想,可以引入使用成熟的包搭建大数据存储与处理环境,进行分布式与高并发编程。而通常3年的C++程序员,对开发规范还没有充分理解,经常碰到不明白的语法特性,在各种奇怪的问题中被考验着细致与耐心。这就是“少即是多”。

