这是一个java语言和C++语言之间的比较。
C++和Java语言之间的不同可以追溯到它们各自的传统,它们有着不同的设计目标。
C++ 被设计成主要用在系统性应用程序设计上的语言,对C语言进行了扩展。对于C语言这个为运行效率设计的过程式程序设计语言, C++ 特别加上了以下这些特性的支持:静态类型的面向对象程序设计的支持、异常处理、RAII以及泛型。另外它还加上了一个包含泛型容器和算法的C++库函数。Java 最开始是被设计用来支持网络计算。它依赖一个虚拟机来保证安全和可移植性。Java 包含一个可扩展的库用以提供一个完整的的下层平台的抽象。Java 是一种静态面向对象语言,它使用的语法类似C++,但与之不兼容。为了使更多的人到使用更易用的语言,它进行了全新的设计。不同的开发目标导致 C++ 和 Java 这两种语言的不同的规则以及设计上的平衡点不同。 如下列出不同点:
C++ | Java |
---|---|
除了一些比较少见的情况之外和C语言兼容 | 没有对任何之前的语言向前兼容。但在语法上受 C/C++ 的影响很大 |
一次编写多处编译 | 一次编写多处运行 |
允许过程式程序设计和面向对象程序设计 | 必须使用面向对象的程序设计方式 |
允许直接调用本地的系统库 | 要通过JNI调用, 或者 JNA |
能直接使用底层系统接口 | 在一个保护模式下的虚拟机中运行 |
只提供对象的类型和类型名 | 是反射的, 允许元程序设计和运行时的动态生成代码 |
有多种二进制兼容标准 (例如:微软和Itanium/GNU) | 一种二进制兼容标准,允许运行时库的正确性检查 |
可选的自动边界检查. (例如: vector 和 string 这两个容器的 at() 方法) | 一般都有做边界检查。HotSpot (java)(Sun 的虚拟机实现) 可以去掉边界检查 |
支持本地的无符号数学运算 | 不支持本地的无符号数学运算 |
对所有的数字类型有标准的范围限制,但字节长度是跟实现相关的。标准化的类型可以用 typdef 定义 (uint8_t, ..., uintptr_t) | 在所有平台上对所有的基本类型都有标准的范围限制和字节长度 |
支持指针,引用,传值调用 | 基本类型总是使用传值调用。对象以可以为空的参考的方式传递(相当于在C++里使用指向 class 或者 struct 参数的指针)。[1] |
显式的存储器管理,但有第三方的框架可以提供垃圾搜集的支持。支持析构函数。 | 自动垃圾搜集(可以手动触发)。没有析构函数的概念,对 finalize() 的使用是不推荐的 |
支持类class,结构struct,联合union,可以在堆栈或者堆里为它们动态分配存储器 | 只支持类别,只在堆中为对象分配存储器。Java SE 6在栈为一些对象分配存储器的使用了逃逸分析的优化方法 |
允许显式的覆盖(也叫重写)类型 | 严格的类型安全,除了变宽的类型转换。Java 1.5 开始支持自动类型包装和解包装(Autoboxing/Unboxing) |
C++库包括:语言支持,诊断工具,常用工具,字符串,本地化,容器,算法,迭代器,数值,输入/输出,C库。Boost库提供了更多的功能,包括线程和网络I/O。用户必须在一大堆(大部分互相不兼容)第三方GUI或者其他功能库中进行选择 | 库在每次 Java 发布新版本的时候都会更新并增强功能。1.6版本支持:本地化,日志系统,容器和迭代器,算法,GUI 程序设计(但没有用到系统的GUI),图形,多线程,网络,平台安全,自省机制,动态类别加载,阻塞和非阻塞的I/O,对于xml、XSLT、MIDI也提供了相关接口或者支持类别,数据库,命名服务(例如 LDAP),密码学,安全服务(例如 Kerberos),打印服务,WEB 服务。SWT 提供了一个系统相关的GUI的抽象 |
大部分运算符可以运算符重载 | 运算符的意义一般来说是不可变的,例外是 + 和 += 运算符被字符串重载了 |
完全的多重继承,包括虚拟继承 | 类别只允许单继承,需要多继承的情况要使用接口 |
支持编译期模板 | 泛型被用来达到和C++模板类似的效果,但由于类型消除它们不能在编译期间从代码被编译成字节码 |
支持函数指针,函数对象,lambda(C++11)和接口 | 没有函数指针机制。替代的概念是接口,Adapter 和 Listener也是被广泛使用的 |
没有标准的代码内嵌文档机制。不过有第三方的软件(例如 Doxygen) | Javadoc 标准文档生成系统 |
const 关键字用来定义不可改变的常量和成员函数 | final 提供了一个限制版本的 const ,等价于 type* const 的对象指针或者 const 的基本类型数据。没有 const 成员函数,也没有const type* 指针的等价物 |
支持 goto 语句 | 支持循环标签(label)和语句块 |
源代码可以写成平台无关的(可以被 Windows、BSD、linux、Mac OS X、Solaris 等编译,不用修改),也可以写成利用平台特有的特性。通常被编译成本地的机器码 | 被编译成Java虚拟机的字节码。和Java平台相关,但是源代码一般来说是不依赖操作系统特有的特性的 |
C++ 是一门强大的语言,设计用在系统程序设计方面。Java语言是设计成简单易用易学习,并有一个强大的跨平台的库。Java库对一个库来说相当的大。但Java并不会提供所在平台的所有特性和接口。C++库简单健壮,提供容器和关联数组的支持。[2]
Foo<1>(3);
,如果 Foo 是一个变量,那么它是一个比较的表达式,但如果 Foo 是一个类模板的名字,那么它会创建一个对象.C++允许名字空间级别的常量,变量和函数. 而所有这样的 Java 声明必须在一个类或者接口当中.在 C++ 的声明中,一个类名可以用来声明一个此类对象的值. Java 里没办法做到这点. 在Java里对象不是值. 在 Java 的声明中,一个类名声明的是对此类的一个对象的引用. 而在 C++ 里与之等价的做法是用 "*" 来声明一个指针.在 C++ 里,"."操作符将一个对象作为一个左操作参数来访问这个对象的成员. 因为对象在 Java 里不是值,所有的对象都通过引用来访问,刚才的做法在 Java 里是无法实现的. 在 Java 里,"." 操作符是将一个对象的引用作为左操作参数来访问这个对象的成员.在C++中和这种做法等价的是 "->".C++ | Java |
---|---|
class Foo { // 声明 Foo 类public: int x; // 成員變量 Foo(): x(0) { // Foo 的构造函数Constructor for Foo, } // 初始化 x int bar(int i) { // 成员函数 bar() return 3*i + x; }}; | class Foo { // 定义类 Foo public int x = 0; // 成员变量, // 以及其值的初始化 public Foo() { // Foo的 构造函数 } public int bar(int i) {// 成员方法 bar() return 3*i + x; }} |
Foo a;// 声明 a 为一个 Foo 类的对象值,// 使用其缺省的构造函数// 如果你想要用其他的构造函数,// 你可以用 "Foo a(args);" | Foo a;// 声明 a 为一个 Foo 类的对象的引用a = new Foo();// 使用缺省的构造函数初始化// 如果你想要用其他的构造函数,// 你可以用 "Foo a = new Foo(args);" |
Foo b = a;// 拷贝 a 的内容到一个新的 Foo 类的变量 b 当中;// 另一种可以选择的语法是 "Foo b(a)" | Foo b = a.clone();// 拷贝所有a这个实例的成员到b,当且仅当,// Foo 实现了一个 public 的 clone() 方法,// 并且 clone() 返回一个新的这个对象的拷贝 |
a.x = 5; // 修改 a 对象 | a.x = 5; // 修改 a 对象 |
cout << b.x << endl;// 输出 0,因为 b 和 a 是两个对象 | System.out.PRintln(b.x);// 输出 0,因为 b 和 a 是两个对象 |
Foo *c;// 声明 c 为指向一个 Foo 类对象的指针(初始值是// 未定义的;可能指向任何地方) | Foo c;// 声明 c 为一个指向 Foo 对象的指针// (如果 c 是一个类的成员,那么初始值为空;// 如果 c 是一个局部变量那么你在使用之前必须// 对它进行初始化) |
c = new Foo();// 将 c 绑定为一个新的 Foo 对象的引用 | c = new Foo();// 将 c 绑定为一个新的 Foo 对象的引用 |
Foo *d = c;// 将 d 绑定为和 c 同一个对象的引用 | Foo d = c;// 将 d 绑定为和 c 同一个对象的引用 |
c->x = 5;// 修改 c 指向的对象 | c.x = 5;// 修改 c 指向的对象 |
a.bar(5); // 对 a 调用 Foo::bar()c->bar(5); // 对 *c 调用 Foo::bar() | a.bar(5); // 对 a 调用 Foo.bar()c.bar(5); // 对 c 调用 Foo.bar() |
cout << d->x << endl;// 输出 5,因为 d 引用的对象和 c 一样 | System.out.println(d.x);// 输出 5,因为 d 引用的对象和 c 一样 |
C++ | Java |
---|---|
const Foo *a; // 你不能通过 a 修改 a 指向的对象 | final Foo a; // 你可以通过 a 修改 a 指向的对象 |
a = new Foo(); | a = new Foo(); // 只能在构造函数里 |
a->x = 5;// 非法 | a.x = 5;// 合法, 你仍然可以修改这个对象 |
Foo *const b = new Foo();// 你可以声明一个 "const" 指针 | final Foo b = new Foo();// 你可以声明一个 "final" 引用 |
b = new Foo();// 非法, 你不能对它再次绑定 | b = new Foo();// 非法, 你不能对它再次绑定 |
b->x = 5;// 合法,你还是可以修改这个对象 | b.x = 5;// 合法,你还是可以修改这个对象 |
goto
语句; Java 强制结构化流程控制( structured control flow), 依赖break标签 和 continue标签 语句来提供类似于 goto 的部分功能. 一些评论者指出这些标签化的流程控制打破了结构化编程的单退出点的特点.[3]C++ 提供了一些 Java 缺乏的低级特性. 在 C++ 里, 指针可以用来操作特定的内存位置, 这是在写低级操作系统模块的时候必须用到的. 类似的, 许多 C++ 编译期支持内联汇编,在 Java 里, 这样的代码只能放在外来的库中,而且在调用的时候只能通过JNI来访问这些外来库提供的接口.if
, while
和 for
里的退出条件)预期的都是一个布尔表达式, 但 if(a = 5)
这样的代码在 Java 里会导致编译错误,因为没有从整型到布尔的隐式变窄转换. 如果代码是 if(a == 5)
的输错的情况那么是很方便发现这个错误的. 而目前的 C++ 编译器一般来说只会针对这种情况产生一个警告.对于传参数给函数的情况, C++ 支持引用传递和值传递. 在 Java 里, 参数总是值传递的.[4] 但在 Java 里,所有的非基本类型的值都只是对于对象的引用 (用 C++ 的术语来说, 它们是智能指针). 对象在 Java 里不是作为值直接被使用的,只有对象的引用可以被直接操作; 习惯于将对象当做值直接使用的 C++ 开发者经常会把这个跟引用传递搞混.Java 内建的类型在字节宽度和取值范围上是被虚拟机定义好的; 在 C++ 里, 内建的类型有定义一个最小取值范围, 但是其他的部分(字节宽度)可以被映射成具体平台上支持的原生类型.举个例子, Java 字符是16位的Unicode字符, 字符串是由这样的字符组成的序列. C++ 提供窄和宽两种字符,但实际的字符宽度是和平台相关的, 视所用的字符集而定. 字符串可以由这两种字符中的一种组成.浮点数及其操作的精度和舍入方式在 C++ 里是平台相关的. Java 提供了一个可选的严格的浮点数模型,保证跨平台的一致性,不过可能会导致运行时效率比较差.在 C++ 里, 指针可以作为内存地址直接操作. Java 没有指针 — 它只有对象引用和数组引用,这两者都不允许直接用来访问内存地址. 在 C++ 里可以构造一个指向指针的指针,而 Java 的引用只能指向对象.在 C++ 里, 指针可以指向函数或者方法(函数指针). 在 Java 里的等价物是对象或者接口的引用.虽然有使用栈内存分配的对象, C++ 还是支持区域资源管理, 一个用来自动管理内存和其他系统资源的技术,此技术支持确定性对象销毁(deterministic object destruction). 不过,区域资源管理在 C++ 里是不被保证的;它只是一个设计模式,所以需要依赖程序员遵守相关的规则. Java 通过使用垃圾搜集来支持自动内存管理,但对于其他的系统资源(窗口,通讯端口,线程),如果垃圾搜集器无法决定它们是否不再被用到,那通常还是需要显式的释放的.C++ 的用户可自定义操作符重载的特性在 Java 里是不支持的. 唯一在 Java 里可以重载的操作符是 "+
" 和 "+=
" 操作符, 在字符串里重载为连接字符串.Java 的标准应用程序接口支持反射和动态加载任意代码.C++ 支持静态和动态的库连接.Java 支持泛型, 其主要目的是提供类型安全的容器. C++ 支持模板, 在泛型编程方面提供了更强的支持.Java 和 C++ 都对基本类型(也叫"内建"类型)和用户自定义类型(也叫"复合"类型). 在 Java 里, 基本类型只有值的语义,复合类型只有引用的语义. 在 C++ 里所有的值都有值语义,可以创建对于任何类型的引用,这样就允许通过引用语义来操作对象.C++ 支持任意类型的多重继承. 在 Java 里一个类只能从单个的类继承而来,但一个类可以实现多个的接口(换句话说,它支持类型的多重继承,但对于实现只能单继承(it supports multiple inheritance of types, but only single inheritance of implementation))。Java 对于类和接口是显式区分的. 在 C++ 里多重继承和纯虚函数使得定义出类似于 Java 的接口的类是可能的,不过会有少许区别.Java 在语言和标准库都对多线程有良好的支持. synchronized
这个 Java 的关键字为了支持多线程应用提供了简单而安全的互斥锁 ,但同步(synchronized)区只能用 LIFO 的顺序离开. Java 也为更高阶的多线程同步提供了健壮而复杂的库. 在 C++ 里没有专门为多线程定义的内存模型; 但第三方库提供了和 Java 差不多的功能; 不过这些 C++ 库之间差异较大,一致性不好.C++ 方法可以声明为虚函数, 虚函数是在运行期根据对象的类型才确定的. C++ 方法缺省情况下不是虚的. 在 Java 里, 方法缺省情况下是虚的, 但可以使用final
关键字使之声明为非虚的.C++ 枚举属于基本类型,支持和其他整数类型之间的转换和比较. Java 枚举实际上是类的实例(它们从 java.lang.Enum<E>
扩展而来),象其他的类一样可以定义构造函数,数据成员及方法.C++ 和 Java 都提供泛型编程的能力,分别是模板 和 泛型(Generics in Java). 虽然它们被创造用来解决类似的问题,有类似的语法,但实际上很不一样.
C++ 模板 | Java 泛型 |
---|---|
类和函数都可以使用模板. | 类和方法都可以使用泛型. |
参数可以是任意类型或者整型. | 参数只能是能被引用的类型(非基本类型). |
在编译的时候对于每种类型生成类或者函数的拷贝. | 对于所有类型的参数,只有一个版本的类或者函数生成. |
同一个类用不同类型生成的对象在运行期也是不同类型的 | 编译完成以后类型参数的类型是被消除的; 同一个类用不同类型参数生成的对象在运行期是相同类型的. |
想要用到模板类或者函数的实现代码的话必须 include 它(只是声明是不够的). | 有一个编译好的类文件里的类或者函数的签名就足以使用泛型了 |
模板可以被具体化 -- 可以为某个特定的模板参数提供单独的实现. | 泛型不能被具体化. |
模板参数可以有缺省参数(default argument)(只针对对于模板类,模板函数是没有此特性的). | 泛型类参数无法拥有缺省参数. |
不支持通配符. 返回的类型经常是嵌套的 typedef 形式的. | 如果只用一次,那么支持通配符作为类型参数. |
不直接支持设置类型参数的边界 (即, 不允许说明类型参数必须为某个类型的子类/父类), 但超编程提供了这个特性[6] | 支持类型参数边界, 分别以 "extends" 和 "super" 来定义上界和下界; 同时允许定义类型参数之间的继承关系 |
允许生成有参模板的类的实例 (如 foo = new Foo<T>, T 为参数) | 不允许生成有参模板类的实例 (除非使用反射) |
泛型类的类型参数无法用在 static 方法和变量上. | |
static 变量不在在不同的类型参数生成的类之间共享. | static 变量在不同类型参数生成的类的对象之间是共享的. |
泛型类和函数在声明时不强制类参数的类限制. 使用错误的类参数会导致模板代码"不工作". 值得注意的是, 如果使用了错误的参数, 则错误信息将出现在定义模板的代码处 (而非调用模板的代码处), 说明 "不支持以该类型作为参数来实例化模板". 这种错误信息往往难以帮助人们找出真正的问题所在 (编程时究竟使用了何种 "错误的" 参数). 因此, 模板类或者函数的正确使用更依赖于正确的文档. 超编程以额外的代价提供了这些特性. | 泛型类和函数在声明的时候强制了类参数的类限制(Generic classes and functions can enforce type relationships for type parameters in their declaration). 使用一个错误的参数会在使用它的时候导致一个类错误. 在泛型代码里操作和参数化类型只能按声明的时候保证安全的方式来使用. 这用失去弹性的代价来换取好得多的类型方面的安全性. |
模板是图灵完全的 (参见 模板超编程). | 泛型不是图灵完全的. |
(a/b)*b + (a%b) == a
. C++ 版本有时候会更快,因为它允许直接使用处理器的截断方式.整型的长度在 Java 里是已定义好的(int 为 32-bit, long 为 64-bit), 而在 C++ 里整型和指针的长度是和编译器以及应用二进制接口相关的. 因此仔细编写的 C++ 代码可以利用64位处理器的能力而又可以在32位处理器上工作. 但是需要很仔细的用可移植的方式编写. 作为对比, Java 的固定整型大小使得程序员无法做到这样,没办法利用处理器的字长会导致 Java 在64位处理器上表现较差.想运行一个编译好的 Java 程序,计算机上要运行JVM;而编译好的 C++ 程序不需要额外的应用。比较早期的 Java 版本在性能上比静态编译的语言如 C++ 差得很多,这是因为用 C++ 是直接编译成一些机器指令,而当 Java 编译成字节码以后用 JVM 解释执行的时候又牵涉了不少额外的机器指令。 例如:
Java/C++ 语句 | C++ 生成的代码 (x86) | Java 生成的字节码 |
---|---|---|
vector[i]++; | mov edx,[ebp+4h] mov eax,[ebp+1Ch]inc dWord ptr [edx+eax*4] | aload_1 iload_2dup2ialoadiconst_1iaddiastore |
C++ 在大部分的情况下都比 Java 要快,[7] 有几个数值方面的基准测试的研究争辩说 Java 在某些情况下可能会比 C++ 的性能好得多。[8][9][10] 但有人说数值方面的基准测试对于语言的评估是不合适的,因为编译器都可以做相关的优化,甚至可能将被测试的代码彻底删除。[11][12][13] 如果涉及到一个真正现实应用的程序,Java 会因为很多原因导致性能变差:[14][15][16]
所有的对象都在堆里被申请。对于使用小对象的函数来说会导致很大的性能损失,因为在栈里申请内存几乎没有性能损失。方法缺省是虚的。这对于小对象来说会因为虚表增加好几倍的内存使用。它也会引起性能损失,因为 JIT 编译器不得不对查虚表的过程做额外的优化。即使使用标准的容器依然会有很多的类型转换,这会引起性能损失,因为需要遍历整个继承树。虚拟机更进一步增加了内存的使用,因此降低了内存的局部性,增加了缓存命中失败率,从而导致整个程序变慢。缺乏低级细节的操作方式使得开发者无法将程序进一步优化,因为编译器不支持。[17]有人争论说,和 Java 相比 C++也有很多劣势:
指针使得优化变得困难,因为它们可能指向任意的数据。当然现在这一点也并非完全正确,因为一些现代的编译器引入了 "严格别名" 的规则 [18] 并且支持 C99 的关键字 restrict,从而严格限制了指针的使用,使其只能用于指向已知的变量 [19]Java 的垃圾搜集和使用malloc/new来申请内存相比能拥有更好的缓存连贯性,因为它的申请一般来说是顺序的。然而,始终有争论认为二者同样会导致内存的“零碎化”(即多次分配和回收之后内存空间会变得不连续),且并没有哪一个比对方有更明显的缓存优势。运行时编译可能可以更好的优化代码,因为可以利用运行时的额外的信息,例如知道代码是在什么样的处理器上运行。然而当今的情况也并非完全如此,因为目前最先进的 C++ 编译器也会针对不同系统生成不同的目标代码,以期充分利用该系统的计算能力 [20]此外,有争议的是,花在更复杂的 C++ 代码上的 debug 时间太多,用 Java 开发完全可以把这些时间用来优化 Java 代码。当然对于一个给定的程序来说两种语言能优化到什么程度也是一方面。最后,对于处理器负担很重的情况,例如视频渲染,C++ 能直接访问硬件,在同样一个硬件规格下 C++ 总是会比 Java 的表现好很多。
C++不是任何一个公司或者组织的商标,不被任何个人拥有。[21] Java原是Sun的商标,现在由甲骨文公司拥有。[22]
C++语言由 ISO/IEC 14882 定义,是一个ISO标准,由 ISO/IEC JTC1/SC22/WG21 委员会发布。 Java语言由 Java Language Specification 定义,这是一本Sun公司(已被甲骨文收购)出版的书。[23]
转自:https://zh.wikipedia.org/wiki/Java%E5%92%8CC%2B%2B%E7%9A%84%E5%B0%8D%E7%85%A7
新闻热点
疑难解答
图片精选