c++ github vs
2015-09-19 12:23:00 weixin_34226182 阅读数 21

前言

其实,一直很想研究一下这样一个命题。但内心一直在怀疑自己是否有把握把这篇文章写好。不管怎样,我今天终于下决心开始这样一个命题。我会持续更新这篇文章,以保持内容的准确性,丰富性和完整性。

Why

可能很多人会疑问,为什么要写这样一篇文章呢?为什么要对比这三门语言呢?
大家都知道,Java/OC/C++都具有面向对象的特性,Java由C++改进而来,Java语言其实可以称之为C++--。因为Java语言其实是在C++的基础上去掉了很多特性。使之成为了一个纯粹的面向对象的语言。而C++语言则是在C语言的基础上添加了面向对象特性改进而来,早期的C++语言还被称之为C With Classes。OC语言和C++一样,也是在C语言的基础上添加了面向对象的特性。从这个角度来看,其实这三种语言具有共同的祖先C语言。并且他们又都具有面向对象的特性。所以,这三门语言的设计难免有很多相似之处,或者说很多值得探讨,非常有趣的地方。这篇文章,我们将从几个不同的角度来对比这三门语言,

历史

1995年,由James Gosling带领的Green团队正式发布了全新的编程语言Java
1980年,Brad Cox发明了新的编程语言Objective-C,最初只是作为C语言的简单扩展
1979年,Bjarne Stroustrup还在上博士期间,就产生了开发一门面向对象语言的想法,也就是现在大名鼎鼎的C++

从语言的历史来看,Java语言的诞生相对OC/C++晚了10年多,而OC,C++几乎是同时代的作品。这也导致了Java语言吸取了很多前期面向对象语言优点。同时,规避了前期面向对象语言的很多缺点。这也是造就了后面Java语言盛况空前的原因之一。

语法

虽然三门语言都源自于C语言,但语法却有着很大的不同。

方法调用

Java语言使用点语法调用方法
C++调用方法需要分不同的情况,如果是对象或者引用,则使用点语法调用。而如果是指针,则使用->调用,从某种层面来说,这也增加了程序员的学习成本,容易给新手产生很多迷惑。
OC的方法调用最特别,使用中括号的方式实现方法的调用。其实,OC通常不把这个叫做方法调用,而称之为给对象发送消息。

从对方法调用的设计上面来看,Java/C++语言设计的相对较为保守,却更容易被人接受,也更容易理解。而OC的设计却颇为前卫,这也导致了后来一批程序员不愿接触这门语言,原因仅仅是因为语法太过奇葩。

Java

public class Bird {
    public void fly() {
        System.out.println("I'm a bird,I can fly...");
    }
    
    public static void main(String[] args) {
        Bird d = new Bird();
        d.fly();
    }
}

OC

Bird.h

#import <Foundation/Foundation.h>

@interface Bird : NSObject
- (void)fly;
@end

Bird.m

@implementation Bird
- (void)fly {
    NSLog(@"I'm a bird,I can fly...");
}
@end

调用

Bird *bird = [[Bird alloc] init];
[bird fly];

C++
Bird.hpp

#ifndef Bird_hpp
#define Bird_hpp

#include <stdio.h>
#include <iostream>

class Bird {
public:
    Bird();
    ~Bird();
    void fly();
};

#endif

Bird.cpp

#include "Bird.hpp"

Bird::Bird() {}

Bird::~Bird() {}

void Bird::fly() {
    std::cout << "I'm a bird,I can fly..." << std::endl;
}

调用

Bird b;
b.fly();

Bird *b = new Bird;
b->fly();

语法特性

OC/C++同时支持面向过程编程,语言设计相对而言更加灵活。但却增加了极大的学习成本。
Java语言是一门纯粹的面向对象的编程语言,使用相对较为简单。

使用一个简单的表格来对比一下三种语言的一些基本特性支持情况,主要对比三者支持不同的情况。

语言特性 Java OC C++
接口 支持 支持 不支持
泛型 支持 (支持)Xcode7以前不支持 支持
运算符重载 不支持 不支持 支持
函数重载 支持 不支持 支持
多重继承 不支持 不支持 支持

在这一轮PK上面,Java语言的语法相对而言,最为简单,但支持的语法特性也最少。C++支持的语言特性较多,难度最大,但灵活性最高。OC位于二者中间。这一轮的PK上,可以认为三者齐平。

学习难度

说到学习难度,毫无疑问,三门语言中难度最大的,非C++莫属。
C++为了增加语言的灵活度,增加了很多其它两门语言没有的特性,例如:运算符重载,模板,函数指针等等,这些特性使用起来都比较麻烦,极大地增加了新手的学习成本。从另一方面来讲,C++有三种内存模型:对象,引用和指针。这也会让新手感到非常迷惑。我曾经在学习这部分知识的时候就非常迷惑。正是由于C++语言设计的复杂,也导致了后来的编译器开发难度很大,导致了大名鼎鼎的GCC编译器体积一再扩大,但支持的依然不是很完美。
OC和Java对比来看的话,我认为OC语言的难度要稍大一些,OC语言自开发出来以后,基本上一直都是苹果公司在支持和完善。到目前版本来说,已经非常完善了。现代OC语言在传统面向对象语言的基础上增加很多特性,例如:Category,Extension,消息转发,方法替换等等。同时,OC还保留它的祖先的部分特性。这些都增加了学习成本。
Java语言相对而言,学习成本是最低的,因为它只有一种内存模型。所有的调用都是通过指针实现的,你甚至都感觉不到指针的存在。
从学习难度的对比来看,Java语言的学习难度最低,最优。OC语言其次,C++学习难度最高,最次。

面向对象特性

在这一轮的PK上,OC/C++语言同时支持面向过程编程,这将不作为我们讨论的内容。
三门语言在面向对象的支持上,大体相同,但略有差异:

Java/OC有接口的概念,Java的接口关键字是interface,接口里面所有的方法都必须实现。
OC的接口关键字是protocol(OC里面其实称之为协议,这里简单起见,统一叫做接口),OC接口里面的方法不一定要全部实现,取决于你的设置。
从接口的设计对比来看,OC稍微好一些。
C++没有接口的概念,但使用纯虚函数实现类似接口的概念。
C++/Java语言支持函数的重载,而OC不支持函数重载,这或多或少算是一种遗憾。

在这一轮的PK中,三门语言的设计不相上下。

性能

在这一轮的PK上,毫无疑问,Java语言的性能是最差的。
对于OC/C++,没有确切的数据说明谁的性能更好。这两者都是基于C语言进行了面向对象的扩展。但是,OC的Runtime可能会消耗一些性能。所以,可能OC的性能会稍弱于C++
在这一轮的PK中,Java最次,OC/C++持平。

文档支持

在自动生成API文档方面,Java语言的JDK自带了文档生成工具,生成的文档非常漂亮,美观。
而苹果官方也为OC提供了一个非常棒的文档生成工具,生成的文档同样非常漂亮。
而C++并没有官方支持的文档生成工具,它需要依赖于第三方的文档生成Doxygen来生成文档。
在这一轮的PK中,Java/OC持平,C++最次

IDE

从IDE的数量上来说,支持C++/Java的IDE较多。
但从IDE调试功能上来说,Java最优,Java IDE都支持变量的预执行,甚至替换执行等等。
而OC/C++ IDE都需要命令行操作才能实现。
从IDE的智能补全来说,其实也毫无疑问,Java IDE支持的最好。
IDE辅助代码检查,修正,错误提示等,这一点上来说,依然是Java IDE支持的最好。其次是OC IDE,最后才是C++。
在这一轮的PK中,Java最优,OC其次,C++最次

可移植性

从可移植性来说,毫无疑问,Java的跨平台性是做的的最好的。Java的口号是:一次编译,到处运行。

而OC/C++需要针对不同的平台编写不同的代码,才能保证在不同的平台上面稳定地运行。

垃圾回收

编程的同学都知道,垃圾回收是编程过程中一件繁琐且容易出错的事情。
在三门语言对垃圾回收的支持上,Java语言有非常成熟的垃圾回收机制,几乎不需要程序员显式处理。
OC有ARC(自动引用计数器),通过引用计数器,也可以较好地实现垃圾回收,并且保证了程序的高效性。但是,如果处理不当,依然有可能出现内存泄露。从设计上来说,并没有Java垃圾回收器智能。
C++不支持垃圾回收,学习C++编程的同学必须牢记的一句话就是:谁创建,谁释放。
在这一轮的PK中,Java的垃圾回收器设计最为智能,但是效率不及OC的ARC。所以,可以认为二者水平相当。而C++不支持自动垃圾回收,最次。

新特性

语法一节,我已经谈到OC语言的设计最为前卫,而Java/C++语言的设计最为保守。
OC语言中的很多新特性让我非常喜欢。

  • 使用@property可以自动生成set/get方法。同时,还可以给set/get方法增加同步,ARC等设置,这极大地简化了代码编写的工作量,减少了很多重复的工作量。
  • OC的Category支持对已有类进行扩展,这可以减少很多冗余的继承类,保证了代码的简洁性。同时,Category并不会破坏原有类的整体一致性。
  • OC的Runtime可以实现轻松地在已有类的方法中插入新的逻辑代码,甚至可以实现方法的替换等等,所谓的面向切面编程(AOP)使用OC语言实现起来最为便捷。
  • OC的Runtime设计基本可以摆脱try...catch,虽然这有利有弊,但确实保证了代码的整洁一致性。

而对于Java,实现特性1要依赖第三方组件,并且实现的没有OC的那么彻底,特性2 Java没有办法实现。实现AOP,Java语言要依赖自己的反射调用,语法较为繁琐,实现难度较大。

相对于C++,上面的所有特性它几乎都不具备,属于一门依然相对较为原始的语言。

总结

C++为了兼容C语言,同时为了提供尽可能高的灵活性,已经使之成为了最难学习的编程语言。同时,由于设计时需要考虑的因素太多,程序员学习的成本太高,导致其背上了陷阱太多的骂名。

最新的C++17标准正在设计当中,依然有一些新的特性加入到C++标准当中。当然,设计人员也开始考虑到C++的一些编程规范和约束的制定。我认为,C++似乎不应该考虑太多的加法,在这个编程语言百花齐放的时代,简单应该是编程语言追求的目标,而不是一味地追求过多的特性,过多冗余的设计,希望C++设计委员会也能够认为到这一点,给C++适当地做做减法。

Java语言在一开始发布的时候,其运行性能受到的极大的质疑,人们纷纷拿它和C语言对比,其结果也是让人大跌眼镜。而经过这么多年的发展,Java语言的性能有了长足的进步。虽然,与C语言还有很大的差距,但它的确在进步。而在吸取其它新语言如Python等语言的新特性上面,也有了一定的进步,希望Java语言未来能够越来越好。

OC语言从发布之初,到如今的最新版本,已经发生了很大的变化。前面的新特性*一节也讲到OC语言的很多新特性让人非常惊喜和赞叹。但不得不吐槽OC的时,无数的@符号实在让人抓狂,还有错误提示稍微有些薄弱。

PS:虽然进行了这么多的对比,但我要说,语言无好坏。真正的程序员,应该是不区分语言的。即所谓的无招胜有招的状态。所以,不要被这篇文章误导,我只是站在三门语言对程序员以及系统构建的友好程度上做了一个简单的对比,希望它不要破坏你对自己喜欢语言的热爱。

参考文档

Oracle
Objective-C History
Cplusplus History

如果你喜欢这篇文章,请到Fork我的github仓库:
https://github.com/yuanhoujun/jianshu.git
如果你对这篇文章有任何的修改建议,请给我发送Pull Request。如果你想给我留言,可以加我的QQ:626306805,如果你想和更多的人一起讨论iOS,请加入iOS交流群:468167089

2009-02-17 08:37:00 iteye_7408 阅读数 22

“作为一名C++程序员,我们早已掌握了面向对象程序设计的基本概念,而且Java的语法无疑是非常熟悉的。事实上,Java本来就是从C++衍生出来的。”

然而,C++和Java之间仍存在一些显著的差异。可以这样说,这些差异代表着技术的极大进步。一旦我们弄清楚了这些差异,就会理解为什么说Java是一种优秀的程序设计语言。本附录将引导大家认识用于区分Java和C++的一些重要特征。

(1) 最大的障碍在于速度:解释过的Java要比C的执行速度慢上约20倍。无论什么都不能阻止Java语言进行编译。写作本书的时候,刚刚出现了一些准实时编 译器,它们能显著加快速度。当然,我们完全有理由认为会出现适用于更多流行平台的纯固有编译器,但假若没有那些编译器,由于速度的限制,必须有些问题是 Java不能解决的。

(2) 和C++一样,Java也提供了两种类型的注释。

(3) 所有东西都必须置入一个类。不存在全局函数或者全局数据。如果想获得与全局函数等价的功能,可考虑将static方法和static数据置入一个类里。注意没有象结构、枚举或者联合这一类的东西,一切只有“类”(Class)!

(4) 所有方法都是在类的主体定义的。所以用C++的眼光看,似乎所有函数都已嵌入,但实情并非如何(嵌入的问题在后面讲述)。

(5) 在Java中,类定义采取几乎和C++一样的形式。但没有标志结束的分号。没有class foo这种形式的类声明,只有类定义。

class aType()

void aMethod() {/* 方法主体 */}

}

(6) Java中没有作用域范围运算符“::”。Java利用点号做所有的事情,但可以不用考虑它,因为只能在一个类里定义元素。即使那些方法定义,也必须在一 个类的内部,所以根本没有必要指定作用域的范围。我们注意到的一项差异是对static方法的调用:使用ClassName.methodName()。 除此以外,package(包)的名字是用点号建立的,并能用import关键字实现C++的“#include”的一部分功能。例如下面这个语句:

import java.awt.*;

(#include并不直接映射成import,但在使用时有类似的感觉。)

(7) 与C++类似,Java含有一系列“主类型”(Primitive type),以实现更有效率的访问。在Java中,这些类型包括boolean,char,byte,short,int,long,float以及 double。所有主类型的大小都是固有的,且与具体的机器无关(考虑到移植的问题)。这肯定会对性能造成一定的影响,具体取决于不同的机器。对类型的检 查和要求在Java里变得更苛刻。例如:

■条件表达式只能是boolean(布尔)类型,不可使用整数。

■必须使用象X+Y这样的一个表达式的结果;不能仅仅用“X+Y”来实现“副作用”。

(8) char(字符)类型使用国际通用的16位Unicode字符集,所以能自动表达大多数国家的字符。

(9) 静态引用的字串会自动转换成String对象。和C及C++不同,没有独立的静态字符数组字串可供使用。

(10) Java增添了三个右移位运算符“>>>”,具有与“逻辑”右移位运算符类似的功用,可在最末尾插入零值。“>>”则会在移位的同时插入符号位(即“算术”移位)。

(11) 尽管表面上类似,但与C++相比,Java数组采用的是一个颇为不同的结构,并具有独特的行为。有一个只读的length成员,通过它可知道数组有多大。 而且一旦超过数组边界,运行期检查会自动丢弃一个异常。所有数组都是在内存“堆”里创建的,我们可将一个数组分配给另一个(只是简单地复制数组句柄)。数 组标识符属于第一级对象,它的所有方法通常都适用于其他所有对象。

(12) 对于所有不属于主类型的对象,都只能通过new命令创建。和C++不同,Java没有相应的命令可以“在堆栈上”创建不属于主类型的对象。所有主类型都只 能在堆栈上创建,同时不使用new命令。所有主要的类都有自己的“封装(器)”类,所以能够通过new创建等价的、以内存“堆”为基础的对象(主类型数组 是一个例外:它们可象C++那样通过集合初始化进行分配,或者使用new)。

(13) Java中不必进行提前声明。若想在定义前使用一个类或方法,只需直接使用它即可——编译器会保证使用恰当的定义。所以和在C++中不同,我们不会碰到任何涉及提前引用的问题。

(14) Java没有预处理机。若想使用另一个库里的类,只需使用import命令,并指定库名即可。不存在类似于预处理机的宏。

(15) Java用包代替了命名空间。由于将所有东西都置入一个类,而且由于采用了一种名为“封装”的机制,它能针对类名进行类似于命名空间分解的操作,所以命名 的问题不再进入我们的考虑之列。数据包也会在单独一个库名下收集库的组件。我们只需简单地“import”(导入)一个包,剩下的工作会由编译器自动完 成。

(16) 被定义成类成员的对象句柄会自动初始化成null。对基本类数据成员的初始化在Java里得到了可靠的保障。若不明确地进行初始化,它们就会得到一个默认 值(零或等价的值)。可对它们进行明确的初始化(显式初始化):要么在类内定义它们,要么在构建器中定义。采用的语法比C++的语法更容易理解,而且对于 static和非static成员来说都是固定不变的。我们不必从外部定义static成员的存储方式,这和C++是不同的。

(17) 在Java里,没有象C和C++那样的指针。用new创建一个对象的时候,会获得一个引用(本书一直将其称作“句柄”)。例如:

String s = new String("howdy");

然 而,C++引用在创建时必须进行初始化,而且不可重定义到一个不同的位置。但Java引用并不一定局限于创建时的位置。它们可根据情况任意定义,这便消除 了对指针的部分需求。在C和C++里大量采用指针的另一个原因是为了能指向任意一个内存位置(这同时会使它们变得不安全,也是Java不提供这一支持的原 因)。指针通常被看作在基本变量数组中四处移动的一种有效手段。Java允许我们以更安全的形式达到相同的目标。解决指针问题的终极方法是“固有方法” (已在附录A讨论)。将指针传递给方法时,通常不会带来太大的问题,因为此时没有全局函数,只有类。而且我们可传递对对象的引用。Java语言最开始声称 自己“完全不采用指针!”但随着许多程序员都质问没有指针如何工作?于是后来又声明“采用受到限制的指针”。大家可自行判断它是否“真”的是一个指针。但 不管在何种情况下,都不存在指针“算术”。

(18) Java提供了与C++类似的“构建器”(Constructor)。如果不自己定义一个,就会获得一个默认构建器。而如果定义了一个非默认的构建器,就不会为我们自动定义默认构建器。这和C++是一样的。注意没有复制构建器,因为所有自变量都是按引用传递的。

(19) Java中没有“破坏器”(Destructor)。变量不存在“作用域”的问题。一个对象的“存在时间”是由对象的存在时间决定的,并非由垃圾收集器决 定。有个finalize()方法是每一个类的成员,它在某种程度上类似于C++的“破坏器”。但finalize()是由垃圾收集器调用的,而且只负责 释放“资源”(如打开的文件、套接字、端口、URL等等)。如需在一个特定的地点做某样事情,必须创建一个特殊的方法,并调用它,不能依赖 finalize()。而在另一方面,C++中的所有对象都会(或者说“应该”)破坏,但并非Java中的所有对象都会被当作“垃圾”收集掉。由于 Java不支持破坏器的概念,所以在必要的时候,必须谨慎地创建一个清除方法。而且针对类内的基础类以及成员对象,需要明确调用所有清除方法。

(20) Java具有方法“过载”机制,它的工作原理与C++函数的过载几乎是完全相同的。

(21) Java不支持默认自变量。

(22) Java中没有goto。它采取的无条件跳转机制是“break 标签”或者“continue 标准”,用于跳出当前的多重嵌套循环。

(23) Java采用了一种单根式的分级结构,因此所有对象都是从根类Object统一继承的。而在C++中,我们可在任何地方启动一个新的继承树,所以最后往往 看到包含了大量树的“一片森林”。在Java中,我们无论如何都只有一个分级结构。尽管这表面上看似乎造成了限制,但由于我们知道每个对象肯定至少有一个 Object接口,所以往往能获得更强大的能力。C++目前似乎是唯一没有强制单根结构的唯一一种OO语言。

(24) Java没有模板或者参数化类型的其他形式。它提供了一系列集合:Vector(向量),Stack(堆栈)以及Hashtable(散列表),用于容纳 Object引用。利用这些集合,我们的一系列要求可得到满足。但这些集合并非是为实现象C++“标准模板库”(STL)那样的快速调用而设计的。 Java 1.2中的新集合显得更加完整,但仍不具备正宗模板那样的高效率使用手段。

(25) “垃圾收集”意味着在Java中出现内存漏洞的情况会少得多,但也并非完全不可能(若调用一个用于分配存储空间的固有方法,垃圾收集器就不能对其进行跟踪 监视)。然而,内存漏洞和资源漏洞多是由于编写不当的finalize()造成的,或是由于在已分配的一个块尾释放一种资源造成的(“破坏器”在此时显得 特别方便)。垃圾收集器是在C++基础上的一种极大进步,使许多编程问题消弥于无形之中。但对少数几个垃圾收集器力有不逮的问题,它却是不大适合的。但垃 圾收集器的大量优点也使这一处缺点显得微不足道。

(26) Java内建了对多线程的支持。利用一个特殊的Thread类,我们可通过继承创建一个新线程(放弃了run()方法)。若将synchronized (同步)关键字作为方法的一个类型限制符使用,相互排斥现象会在对象这一级发生。在任何给定的时间,只有一个线程能使用一个对象的 synchronized方法。在另一方面,一个synchronized方法进入以后,它首先会“锁定”对象,防止其他任何synchronized方 法再使用那个对象。只有退出了这个方法,才会将对象“解锁”。在线程之间,我们仍然要负责实现更复杂的同步机制,方法是创建自己的“监视器”类。递归的 synchronized方法可以正常运作。若线程的优先等级相同,则时间的“分片”不能得到保证。

(27) 我们不是象C++那样控制声明代码块,而是将访问限定符(public,private和protected)置入每个类成员的定义里。若未规定一个“显 式”(明确的)限定符,就会默认为“友好的”(friendly)。这意味着同一个包里的其他元素也可以访问它(相当于它们都成为C++的 “friends”——朋友),但不可由包外的任何元素访问。类——以及类内的每个方法——都有一个访问限定符,决定它是否能在文件的外部“可见”。 private关键字通常很少在Java中使用,因为与排斥同一个包内其他类的访问相比,“友好的”访问通常更加有用。然而,在多线程的环境中,对 private的恰当运用是非常重要的。Java的protected关键字意味着“可由继承者访问,亦可由包内其他元素访问”。注意Java没有与C+ +的protected关键字等价的元素,后者意味着“只能由继承者访问”(以前可用“private protected”实现这个目的,但这一对关键字的组合已被取消了)。

(28) 嵌套的类。在C++中,对类进行嵌套有助于隐藏名称,并便于代码的组织(但C++的“命名空间”已使名称的隐藏显得多余)。Java的“封装”或“打包” 概念等价于C++的命名空间,所以不再是一个问题。Java 1.1引入了“内部类”的概念,它秘密保持指向外部类的一个句柄——创建内部类对象的时候需要用到。这意味着内部类对象也许能访问外部类对象的成员,毋需 任何条件——就好象那些成员直接隶属于内部类对象一样。这样便为回调问题提供了一个更优秀的方案——C++是用指向成员的指针解决的。

(29) 由于存在前面介绍的那种内部类,所以Java里没有指向成员的指针。

(30) Java不存在“嵌入”(inline)方法。Java编译器也许会自行决定嵌入一个方法,但我们对此没有更多的控制权力。在Java中,可为一个方法使用final关键字,从而“建议”进行嵌入操作。然而,嵌入函数对于C++的编译器来说也只是一种建议。

(31) Java中的继承具有与C++相同的效果,但采用的语法不同。Java用extends关键字标志从一个基础类的继承,并用super关键字指出准备在基 础类中调用的方法,它与我们当前所在的方法具有相同的名字(然而,Java中的super关键字只允许我们访问父类的方法——亦即分级结构的上一级)。通 过在C++中设定基础类的作用域,我们可访问位于分级结构较深处的方法。亦可用super关键字调用基础类构建器。正如早先指出的那样,所有类最终都会从 Object里自动继承。和C++不同,不存在明确的构建器初始化列表。但编译器会强迫我们在构建器主体的开头进行全部的基础类初始化,而且不允许我们在 主体的后面部分进行这一工作。通过组合运用自动初始化以及来自未初始化对象句柄的异常,成员的初始化可得到有效的保证。

1045页程序

(32) Java中的继承不会改变基础类成员的保护级别。我们不能在Java中指定public,private或者protected继承,这一点与C++是相 同的。此外,在衍生类中的优先方法不能减少对基础类方法的访问。例如,假设一个成员在基础类中属于public,而我们用另一个方法代替了它,那么用于替 换的方法也必须属于public(编译器会自动检查)。

(33) Java提供了一个interface关键字,它的作用是创建抽象基础类的一个等价物。在其中填充抽象方法,且没有数据成员。这样一来,对于仅仅设计成一 个接口的东西,以及对于用extends关键字在现有功能基础上的扩展,两者之间便产生了一个明显的差异。不值得用abstract关键字产生一种类似的 效果,因为我们不能创建属于那个类的一个对象。一个abstract(抽象)类可包含抽象方法(尽管并不要求在它里面包含什么东西),但它也能包含用于具 体实现的代码。因此,它被限制成一个单一的继承。通过与接口联合使用,这一方案避免了对类似于C++虚拟基础类那样的一些机制的需要。

为创建可进行“例示”(即创建一个实例)的一个interface(接口)的版本,需使用implements关键字。它的语法类似于继承的语法,如下所示:

1046页程序

(34) Java中没有virtual关键字,因为所有非static方法都肯定会用到动态绑定。在Java中,程序员不必自行决定是否使用动态绑定。C++之所 以采用了virtual,是由于我们对性能进行调整的时候,可通过将其省略,从而获得执行效率的少量提升(或者换句话说:“如果不用,就没必要为它付出代 价”)。virtual经常会造成一定程度的混淆,而且获得令人不快的结果。final关键字为性能的调整规定了一些范围——它向编译器指出这种方法不能 被取代,所以它的范围可能被静态约束(而且成为嵌入状态,所以使用C++非virtual调用的等价方式)。这些优化工作是由编译器完成的。

(35) Java不提供多重继承机制(MI),至少不象C++那样做。与protected类似,MI表面上是一个很不错的主意,但只有真正面对一个特定的设计问 题时,才知道自己需要它。由于Java使用的是“单根”分级结构,所以只有在极少的场合才需要用到MI。interface关键字会帮助我们自动完成多个 接口的合并工作。

(36) 运行期的类型标识功能与C++极为相似。例如,为获得与句柄X有关的信息,可使用下述代码:

X.getClass().getName();

为进行一个“类型安全”的紧缩造型,可使用:

derived d = (derived)base;

这与旧式风格的C造型是一样的。编译器会自动调用动态造型机制,不要求使用额外的语法。尽管它并不象C++的“new casts”那样具有易于定位造型的优点,但Java会检查使用情况,并丢弃那些“异常”,所以它不会象C++那样允许坏造型的存在。

(37) Java采取了不同的异常控制机制,因为此时已经不存在构建器。可添加一个finally从句,强制执行特定的语句,以便进行必要的清除工作。Java中的所有异常都是从基础类Throwable里继承而来的,所以可确保我们得到的是一个通用接口。

1047页程序

(38) Java的异常规范比C++的出色得多。丢弃一个错误的异常后,不是象C++那样在运行期间调用一个函数,Java异常规范是在编译期间检查并执行的。除 此以外,被取代的方法必须遵守那一方法的基础类版本的异常规范:它们可丢弃指定的异常或者从那些异常衍生出来的其他异常。这样一来,我们最终得到的是更为 “健壮”的异常控制代码。

(39) Java具有方法过载的能力,但不允许运算符过载。String类不能用+和+=运算符连接不同的字串,而且String表达式使用自动的类型转换,但那是一种特殊的内建情况。

(40) 通过事先的约定,C++中经常出现的const问题在Java里已得到了控制。我们只能传递指向对象的句柄,本地副本永远不会为我们自动生成。若希望使用 类似C++按值传递那样的技术,可调用clone(),生成自变量的一个本地副本(尽管clone()的设计依然尚显粗糙——参见第12章)。根本不存在 被自动调用的副本构建器。为创建一个编译期的常数值,可象下面这样编码:

static final int SIZE = 255

static final int BSIZE = 8 * SIZE

(41) 由于安全方面的原因,“应用程序”的编程与“程序片”的编程之间存在着显著的差异。一个最明显的问题是程序片不允许我们进行磁盘的写操作,因为这样做会造成从远程站点下载的、不明来历的程序可能胡乱改写我们的磁盘。随着Java 1.1对数字签名技术的引用,这一情况已有所改观。根据数字签名,我们可确切知道一个程序片的全部作者,并验证他们是否已获得授权。Java 1.2会进一步增强程序片的能力。

(42) 由于Java在某些场合可能显得限制太多,所以有时不愿用它执行象直接访问硬件这样的重要任务。Java解决这个问题的方案是“固有方法”,允许我们调用 由其他语言写成的函数(目前只支持C和C++)。这样一来,我们就肯定能够解决与平台有关的问题(采用一种不可移植的形式,但那些代码随后会被隔离起 来)。程序片不能调用固有方法,只有应用程序才可以。

(43) Java提供对注释文档的内建支持,所以源码文件也可以包含它们自己的文档。通过一个单独的程序,这些文档信息可以提取出来,并重新格式化成HTML。这无疑是文档管理及应用的极大进步。

(44) Java包含了一些标准库,用于完成特定的任务。C++则依靠一些非标准的、由其他厂商提供的库。这些任务包括(或不久就要包括):

■连网

■数据库连接(通过JDBC)

■多线程

■分布式对象(通过RMI和CORBA)

■压缩

■商贸

由于这些库简单易用,而且非常标准,所以能极大加快应用程序的开发速度。

(45) Java 1.1包含了Java Beans标准,后者可创建在可视编程环境中使用的组件。由于遵守同样的标准,所以可视组件能够在所有厂商的开发环境中使用。由于我们并不依赖一家厂商的方案进行可视组件的设计,所以组件的选择余地会加大,并可提高组件的效能。除此之外,Java Beans的设计非常简单,便于程序员理解;而那些由不同的厂商开发的专用组件框架则要求进行更深入的学习。

(46) 若访问Java句柄失败,就会丢弃一次异常。这种丢弃测试并不一定要正好在使用一个句柄之前进行。根据Java的设计规范,只是说异常必须以某种形式丢弃。许多C++运行期系统也能丢弃那些由于指针错误造成的异常。

(47) Java通常显得更为健壮,为此采取的手段如下:

■对象句柄初始化成null(一个关键字)

■句柄肯定会得到检查,并在出错时丢弃异常

■所有数组访问都会得到检查,及时发现边界违例情况

■自动垃圾收集,防止出现内存漏洞

■明确、“傻瓜式”的异常控制机制

■为多线程提供了简单的语言支持

■对网络程序片进行字节码校验

2017-03-21 11:39:06 qq_19399235 阅读数 379
2018-04-17 15:49:11 youshijian99 阅读数 207

普通变量的引用

#include <Windows.h>
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{

	int a = 10;
	int &b = a;		// 引用
	b = 20;
	printf("a = %d\n", a);

	getchar();
	return 0;
}

指针类型变量的引用

#include <Windows.h>
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{

	int a = 10;
	int *b = &a;
	int *&c = b;
	*c = 20;
	printf("a = %d\n", a);

	getchar();
	return 0;
}
引用作为函数参数
#include <Windows.h>
#include <iostream>
using namespace std;

void func1(int *x, int *y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

void func2(int &x, int &y)
{
	int tmp = x;
	x = y;
	y = x;

}


int main(int argc, char **argv)
{
	int x = 10;
	int y = 20;

	func1(&x,&y);
	printf("x = %d, y = %d\n", x, y);
	func2(x,y);
	printf("x = %d, y = %d\n", x, y);

	getchar();
	return 0;
}

运行结果

x = 20, y = 10
x = 10, y = 10

2018-04-13 17:40:52 youshijian99 阅读数 294

获取当前时间

#include <Winbase.h> 或者 #include<Windows.h>

void WINAPI GetLocalTime(
  __out LPSYSTEMTIME lpSystemTime
);

typedef struct _SYSTEMTIME {  
	WORD wYear;  		/* 有效值 1601 到 30827 */
	WORD wMonth;  		/* 1(January),2(February),3(March),4(April),5(May),6(June),7(July),8(August),9(September),10(October),11(November),12(December) */
	WORD wDayOfWeek; 	/* 0(Sunday), 1(Monday), 2(Tuesday), 3(Wednesday), 4(Thursday), 5(Friday), 6(Saturday)*/ 
	WORD wDay;  		/* 有效值 1 到 31 */
	WORD wHour;  		/* 有效值 0 到 23 */
	WORD wMinute;  		/* 有效值 0 到 59 */
	WORD wSecond;  		/* 有效值 0 到 59 */
	WORD wMilliseconds;	/* 有效值 0 到 999 */
} SYSTEMTIME,  *PSYSTEMTIME;

例程

#include <Windows.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	SYSTEMTIME sysTime;
	GetLocalTime(&sysTime);
	printf("%d/%d/%d/%d - %d:%d:%d:%d\n", sysTime.wYear, sysTime.wMonth, sysTime.wDay
			,sysTime.wDayOfWeek, sysTime.wHour, sysTime.wMinute, sysTime.wSecond
			,sysTime.wMilliseconds);
	getchar();
	return 0;
}

原子操作,变量增1

#include<Winbase.h> 或 #include <Windows.h>

LONG __cdecl InterlockedIncrement(
  __in_out LONG volatile* Addend
);

例程

#include <Windows.h>
#include <stdio.h>

int main(int argc, char **argv)
{

	long inc = 0;
	InterlockedIncrement(&inc);
	printf("inc = %d\n", inc);

	getchar();
	return 0;
}

互斥锁

#include <Winbase.h> 或者 #include <Windows.h>
HANDLE WINAPI CreateMutex(
  __in LPSECURITY_ATTRIBUTES lpMutexAttributes,	// 属性
  __in BOOL bInitialOwner,	// true 互斥体的创建者为拥有者, false 互斥体的创建者不拥有
  __in LPCTSTR lpName	// 互斥锁的名称
);
返回值:
	成功,返回互斥锁的句柄
	失败,返回NULL
	互斥锁的 lpName 名称存在  GetLastError 返回 ERROR_ALREADY_EXISTS 

例程

#include <Windows.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	HANDLE hMutex = NULL;
	hMutex = CreateMutex(NULL, FALSE, (LPCTSTR)"TestMutex");
	if ( GetLastError() == ERROR_ALREADY_EXISTS )
	{
		printf("TestMutex already exist\n");
		CloseHandle(hMutex);
		hMutex = NULL;
	}
	 

	CloseHandle(hMutex);
	getchar();
	return 0;
}

查看当前运行进程路径(不包括文件名)

DWORD WINAPI GetCurrentDirectory(
  __in DWORD nBufferLength,		// lpBuffer 的大小
  __out LPTSTR lpBuffer		// 返回路径内容
);
返回值
	成功,返回写入lpBuffer的字节数
	失败,返回0
查看当前运行进程路径(包括文件名)
DWORD WINAPI GetModuleFileName(
  __in HMODULE hModule,		// NULL, 获取当前进程的路径
  __out LPTSTR lpFilename,	// 返回路径名
  __in DWORD nSize		// lpFilename 的大小
);
返回值
	成功,返回写入 lpFilename 的字节数
	失败,返回0

例程

#include <windows.h> 
#include <stdio.h>
 
int main(int argc, char* argv[])
{
    char szCurPath[128] ;
   GetCurrentDirectory(sizeof(szCurPath),  szCurPath);
   printf("%s\n", szCurPath);
   
   GetModuleFileName(NULL,szCurPath, sizeof(szCurPath));
   printf("%s\n", szCurPath);

  getchar();
  return 0;
}
注意:如果打印不出来,修改

追加字符串

char *strcat( char *strDestination, const char *strSource );
返回值
	返回拼接后的字符串,错误无返回值
例程
#include <Windows.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	char szBuf[128] = "hello";
	//strcat(szBuf, " world!");
	printf("%s\n", strcat(szBuf, " world!"));
	printf("%s\n", szBuf);

	getchar();
	return 0;
}

查看路径是否为目录

DWORD GetFileAttributes(  LPCTSTR lpFileName 	// 路径名
); 	
返回值:
	FILE_ATTRIBUTE_ARCHIVE
	FILE_ATTRIBUTE_COMPRESSED
	FILE_ATTRIBUTE_DIRECTORY		目录
	FILE_ATTRIBUTE_ENCRYPTED
	FILE_ATTRIBUTE_HIDDEN
	FILE_ATTRIBUTE_INROM
	FILE_ATTRIBUTE_NORMAL
	FILE_ATTRIBUTE_READONLY
	FILE_ATTRIBUTE_ROMMODULE
	FILE_ATTRIBUTE_ROMSTATICREF
	FILE_ATTRIBUTE_SYSTEM
	FILE_ATTRIBUTE_TEMPORARY

例程

#include <Windows.h>
#include <stdio.h>


int main(int argc, char **argv)
{
	if ( GetFileAttributes("./") == FILE_ATTRIBUTE_DIRECTORY) {
		printf("current path is directory\n");
	} else {
		printf("current path is not directory\n");
	}
	

	getchar();
	return 0;
}

ifstream 用法

#include <fstream>
#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
    ifstream ifs("./basic_ifstream_class.txt");
    if (!ifs.bad())
    {
        cout << ifs.rdbuf();	// 输出读取的文件内容
        ifs.close();
    }

	getchar();
	return 0;
}
// ./basic_ifstream_class.txt
hello


#include <fstream>
#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
    std::ifstream ifs("./basic_ifstream_class.txt", std::ifstream::in);
    if (ifs.good())
    {
        cout << ifs.rdbuf();	// 输出读取的文件内容
        ifs.close();
    }

	getchar();
	return 0;
}








java vs c++

阅读数 387

C++ Vs 魔兽争霸

阅读数 629

VS下C++项目管理

阅读数 512

C VS C++

阅读数 206

VS C++ 创建线程

阅读数 1982

没有更多推荐了,返回首页