关于C++与Java一些备忘录——基本语法

只是把我个人觉得一些比较有存在感的Java和C++的观念与异同点作一些讨论。

类相关的语法糖:

Java与C++有着同一个前辈——C语言,因此二者在语法上有着惊人的相似性,不论知晓哪一个,试图阅读另一个的代码都不会有什么大问题(尤其是C++11以后的C++代码,借鉴了大量的Java关键字),二者在一些细节上还是存在一些差异,下列列举的语法差异主要围绕class的细节以及一些相关关键字上。我个人觉得试图去硬记这些差异是不明智的,然而一些差异背后的意图仍然是值得思考与研究的。

1.Java任意class型别(8个原生类型之外的类型,Java一个特色在于把他们全部首字母大写了)的类型均为引用,但是最好把它理解成指针(适当的忽略语法细节)。
原因在于Java允许空引用,这对应的应该是指针,此外,引用必须在new后方可视为拥有对象,同样对应的是指针。另一个区别是没有delete,但这不属于类的问题。

2.每个物件(函数或者数据成员)单独赋予访问符——这属于细节差异,个人觉得Java的这个规则有些啰嗦了。对于没有章法的人,很可能会因为这个特性而在Java中肆意无序排列访问符,C++反而能从侧面约束他们,可能用Java的人有别的看法,这个问题仁者见仁。

3.final与const:我觉得这个差别不太好归类,毕竟C++的const到处都在用。总而言之,Java的final承担了对数据的const,对类(或者其中的函数)的不可继承语义。C++的const在修饰函数或变量时与final作用一致。针对不可继承语义,在C++11后引入了对类的final关键字。

4.构造函数:Java喜欢叫它构造器,这个东西在作用上二者几乎没有差异,一个比较特殊的地方在于Java的构造器可以调用同类中的同名构造器(被重载的那个),一开始我觉得这算一个创举,后来想想其实这个问题于C++也好解决,无非就是公共函数。但总的来说Java的这个设定还是很亲民的。
此外,Java拥有所谓初始化块的设定,在写法上提供了一定的灵活性,但其实和写进构造器效果是一致的。初始化块可以是静态初始化块,意义等同于C++类中的的静态变量定义。

5.默认初始化:就是直接在类中给成员赋初值,Java没有初始化列表,所以这个功能理所当然。C++同样在C++11后支持了这个功能,但所有教科书都会告诉你——等价于初始化列表,无需多做讨论。

6.拷贝:首先请参考第一条,因为Java的类变量实际上是引用,所以拷贝构造函数的地位很尴尬。Java提供了函数clone来完成深拷贝,值得注意的一点在于对返回值做赋值时如果忽略了这一点,会直接把内部成员(的引用)暴露出去,除非目的如此,否则往往会扔一个clone出去。对于学习Java的C++编码者来说,这个问题相当的具有迷惑性。

7.析构:Java有垃圾回收(GC),所以没有析构一说,C++则相反。这一条没什么需要解释的。关于要不要有GC,这同样属于仁者见仁的问题。

8.值传递:同样请参考第一条,Java的教材会反复强调并不存在引用传递,即使是类引用也会触发值传递。这对于C++来谈倒是很好理解(感谢指针),对于Java和C++均可以这样理解:原生类型执行变量的值传递,指针以及引用类型执行指针(C++会把引用退化为指针,对于Java还是引用)的值传递。

9.static关键字:一句话,C++存在面向过程的需求,所以static在C++中具有的额外作用均是与函数有关的——局部变量单次初始化,变量的单个文件内限制(对应extern),在类中其功能于Java是一致的。

10.一个关键区别——Java的class可以有一个main函数,而且一般被写成public static void main,意义不需要多解释,实际上,每个类都可以有一个main函数,而只有被直接使用的那个类会触发main。理论上C++可以这样写,但没有什么意义。

11.关于import:这个看完之后我惊讶地发现我也陷入了误区。C++如果试图使用一个库必须include,而java的import只是提供语法便捷(和using类似的功用),也就是说Java的编译器一开始就知道所有库(或者包)的情况,理论上可以提供列出全名而免去所有的import。

继承与多态:

面向对象的目的引入了继承体系以及与之密切相关的多态机制。Java和C++在实现上有着各自的考量。对于多态而言,本质为数据导向的函数调用,C++的具体实现是vtable与vptr。Java在这一点上异曲同工。就《深度探索C++对象模型》中的讨论来看,C++在多重继承与虚继承上花费了大量心思,而Java——直接舍弃了多重继承与虚继承。同时引入了接口用于实现类似多重继承的形式。

1.继承的语法差别:微不足道的区别,但是Java是没有多继承一说的,也就自然没有虚拟继承。这个问题我没有什么发言权,但是从我看到的东西来看,C++的多重继承收到的诟病多于褒奖。一个典型的例子便是stream相关的菱形体系,然而即使是《C++ Primer》作者本人也认为这个设计是存在弊端的。同时Java通过接口的设定侧面支持了多重继承(扩展)的概念。

2.多态的行为模式:简单的说,C++默认非多态,而Java默认多态。造成这个区别的原因依然可以参阅类语法部分的第1条——相当于Java每一次对类的方法调用都通过引用进行,引发多态也是理所当然。从我个人的感觉来看,试图向Java用户解释C++的多态规则远要比向C++用户解释Java多态规则要麻烦(当然这主要还是因为C++的多态太麻烦了)。
由于其默认多态,自然会引发的问题便是它如何采取非多态策略,解决办法是通过super来调用基类对应函数(python也是一样的规则)。此外,通过final关键字可以禁止对函数的子类覆盖,也是一个非多态的策略。

3.类型转换:首先两门语言在功能需求上是一致的,行为上各有千秋。C++的标准手段是dynamic_cast,这个函数兼具了判别和转型两个功能,用起来更加流畅。而Java则是提供了关键字instanceof来做一个类型的继承判别,因此Java一般会多写1-2行代码。这里我个人觉得这个关键字虽然少了一些方便,但多了一些泛用性。类型检查可能会处于别的目的,将其功能予以独立应该也是经过考量的结果。
Java的全部对象默认继承于Object,这个基类规范了一些必要的通用方法,这在抽象体系上提供了一些赏心悦目的保障。而C++并没有这样的设定,所有的指针可以转为void是C的解决之道,然而void几乎什么都做不了,实际上将工作都推给了开发者。

4.抽象类:主要是语法差异。Java提供关键字abstract来辨识抽象类和方法,C++则简单地用=0来标识纯虚函数,其拥有者为抽象类。

5.protected关键字:Java的protected在C++的基础上允许同一文件中的其他类对基类进行访问,这带来了一些问题。总的来说,Java的访问限制没有C++那么分明。

6.反射/RTTI:有的人会觉得Java有反射而C++没有。Java反射的实质是运行时类型识别(RTTI),而一门支持多态的语言理所当然的会支持这个特性。相对而言反射在写法上更好理解一些(RTTI本身就是一个很难简洁优雅的功能,说反射好理解也只是相对C++而言)。反射提供了getClass/Fields/Methods等一些类分析类的方法,能获取较为完整的类信息。C++RTTI的主要方法是tydeid(跨平台有差异),auto/decltype(C++11以后),原理均是依赖于类中隐式创建的typeinfo条目。

泛型解决方案:
泛型编程可谓是工程实践中的一大利器。C++实现了模板,并提供了STL的诸多组件用于支持泛型编程的种种要求。而Java拥有泛型体系以及对应的相关解决方案。两种语言在写法上存在着惊人的一致性,有趣之处在于二者在本质上存在着质的差异。由于Java编译——解释的机制使得其在泛型实现能够采用不同于模板的另一套方案。至于STL,我认为这即使抛开C++单独讨论也可认为是一大创举,至少对于今天的C++初学者而言,所写的代码中不含STL几乎是不可能的事情,更别说那些复杂的大型项目了。

1.Java没有模板实例化过程:所有区别均源于此。一个直观的例子:求和函数的整型与浮点数泛型实现:C++的模板会在使用之后为两种类型分别生成两份函数代码,换言之,所有类型在编译期被唯一确定,同时模板的实例化将引发代码膨胀,但保证了效率。Java的方式被视为擦除——并不提供实例化,而是将类型替换为Object(在擦除前检查类型,并按需求插入类型转换的代码),由于并不会生成多份代码(利用类型转换来过渡),自然不会出现代码膨胀。

2.Java引入了桥方法:我认为桥方法并不算一个闪光点,只是为了解决Java泛型漏洞的必然方案。当混合使用继承以及泛型时,擦除与多态会产生冲突,出现两个可调用的函数,同时诱使多态行为去调用那个错误的函数,为了回归正轨,桥方法应运而生。它在类型为Object的函数中去调用那个正确类型的函数,从而讲结果引回正确之路。这个名字确实也恰如其分。而C++由于通过实例化来构建代码,因此并没有桥方法的出场机会。

3.泛型与反射/模板与RTTI:这个问题似乎应该避免被讨论。我之所以写在这里,是觉得这两个属性恰好构成了某种奇特的互逆——一个试图消除类型,而另一个试图找回类型。然而不得不承认,这个过程太容易犯错了(书上也是怎么写的),于Java而言,泛型完成了类型擦除,反射可以认出这应当是一个泛型(但不能认出尖括号里实际写入的类型了),一个比较好的理解方式为:擦除这个行为本身被编译器记录下来了,因此反射能够找回一定的信息(虽然不一定有意义)。C++的RTTI在这里要简单得多,因为所有类型编译期唯一确定(可以认为,编译后的代码找不到模板的痕迹,虽然会出现由于实例化而导致的代码膨胀),decltype找到的必然是那个正确的类型。

4.STL与容器:STL可谓是C++的一大创举,包含了容器,算法,适配器等诸多工具,同时STL与模板密不可分。Java没有被称为STL(或类似称呼)的物件,java.util算是对应的库包。这一类工具的设计目的可谓相当明了,同时不论Java或C++的书籍都会鼓励你尽量使用他们来解决问题——而不是试图自己发明这些东西。一个好消息是,不论是C++还是Java,在这方面的研发都没有停滞,C++11开始引入了一系列新特性(functinal,lambda, variadic template)来强化STL,而Java也没有忘记其在多线程上的初心,扩展了一系列用于解决不同线程场景的同质异构容器。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容