设计目标[编辑]
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]
语言特性[编辑]
语法[编辑]
- Java语法是上下文无关文法,可以用一个简单的LALR语法分析器来分析.而分析C++就复杂多了;例如
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++ 里,声明一个指向常量的指针是可能的, 也就是说, 你不能修改这个指针指向的对象的内容. 函数和方法也都保证不会修改用 "const" 关键字的指针指向的对象的内容,是强制常量正确性的. 在 Java 里这是不可能做到的. 你可以声明一个引用为 "final"(就像在 C++ 里声明一个 "const" 指针), 但这只是阻止你重新绑定这个引用; 你还是可以修改这个 "final" 引用指向的对象的.
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; // 合法,你还是可以修改这个对象 |
- C++ 支持
goto
语句; Java 强制结构化流程控制( structured control flow), 依赖break标签 和 continue标签 语句来提供类似于 goto 的部分功能. 一些评论者指出这些标签化的流程控制打破了结构化编程的单退出点的特点.[3] - C++ 提供了一些 Java 缺乏的低级特性. 在 C++ 里, 指针可以用来操作特定的内存位置, 这是在写低级操作系统模块的时候必须用到的. 类似的, 许多 C++ 编译期支持内联汇编,在 Java 里, 这样的代码只能放在外来的库中,而且在调用的时候只能通过JNI来访问这些外来库提供的接口.
语义[编辑]
- C++ 允许给函数/方法的参数设置缺省值, Java 不提供这个特性. 但是方法重载可以达到同样的效果.
- C++ 里最小的编译单位是一个函数; Java 里最小的编译单位是一个类. 在 C++ 里, 函数可以被单独编译. 在 Java 里, 要编译和维护单独的方法需要把它们移到超类或子类或者使用其他的代码重构的技巧.
- C++ 允许基本类型之间的一些隐式的转换, 也允许程序员对于用户自定义类型相关的隐式转换规则. 在 Java 里, 只有基本类型之间变宽类型的转换可以是隐式的; 其余的转换需要显式的类型转换语法.
- 这造成的一个后果是,虽然在 Java 和 C++ 里循环的条件(
if
,while
和for
里的退出条件)预期的都是一个布尔表达式, 但if(a = 5)
这样的代码在 Java 里会导致编译错误,因为没有从整型到布尔的隐式变窄转换. 如果代码是if(a == 5)
的输错的情况那么是很方便发现这个错误的. 而目前的 C++ 编译器一般来说只会针对这种情况产生一个警告.
- 这造成的一个后果是,虽然在 Java 和 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>
扩展而来),象其他的类一样可以定义构造函数,数据成员及方法.
资源管理[编辑]
- Java 提供了自动化的垃圾搜集. 在 C++ 里内存管理通常通过构造函数,析构函数以及智能指针。C++ 标准允许垃圾搜集,但并不强制要求; 实际使用上垃圾搜集极少被用到. 强制使用自动垃圾搜集导致了在 Java 里编写实时软件是困难的.[3]
- C++ 可以申请任意的内存块.Java 只能通过对象实例化来申请内存. (注意:在 Java 里, 程序员可以通过创建一个字节数组模拟申请任意的内存块. 不过 Java 数组仍然是对象.)
- Java 和 C++ 在资源管理上使用不同的习语. Java 主要依赖只能回收内存的垃圾搜集机制,因为该机制如果用于回收使用中的非内存的系统资源可能是非常危险的。而 C++ 主要依赖 RAII (资源的获取就是初始化). 这反映了这两种语言的几方面的不同:
- 在 C++ 里在栈里申请复合类型的对象是很平常的,一旦退出栈的范围就会被销毁. 在 Java 里复合类型的对象总是在堆里申请的内存,而后被垃圾搜集器搜集 (除非在虚拟机里使用了逃逸分析技术来将堆的内存申请转成栈的.
- C++ 有析构函数, 而 Java 有finalizer(en:finalizer). 两者都会在对象释放之前被调用, 但是它们有显著的不同. 一个 C++ 对象的析构函数必须被隐式(栈变量对象的情况)或者显式地调用来释放对象. 析构函数在对象释放之前同步地执行. 同步,协调的反初始化以及释放在 C++ 里满足 RAII 的要求. 在 Java 里, 对象的释放是被垃圾搜集器隐式处理的. 一个 Java 对象的 finalizer 在它被最后一次访问之后和在实际释放之前的某个时间点被异步(en:asynchrony)地调用, 这个调用有可能一直不产生. 非常少的对象需要 finalizer; 只有那些在释放前必须保证一些清理工作一定要做的对象来说才是需要的 — 典型的情况是:释放对 JVM 来说是外部的资源. 在 Java 里,企图安全同步的释放某些系统资源,只能用显式的 try/finally 结构来进行.
- 在 C++ 里是有可能有一个迷途指针的 – 过时的对一个已释放的对象的引用(en:reference (computer science)); 试图使用一个迷途指针的结果是导致程序错误. 在 Java 里, 垃圾搜集器不会销毁一个正在被引用的对象.
- 在 C++ 里未初始化过的基本类型对象是有可能存在的, Java 强制要做缺省初始化.
- 在 C++ 里有可能申请了一个对象,但对它没有任何引用. 这样的不可达对象(en:unreachable object)是无法被销毁的,导致了内存泄漏. 作为对比, 在 Java 里一个对象不会被回收直到它变得不可达(对于用户程序来说). (注意: 弱引用(en:weak reference) 是被支持的, 这个特性让 Java 的垃圾搜集器能够识别不同 程度的可达性.) 在 Java 里垃圾搜集阻止了很多内存泄漏的情况, 但某些情况下泄漏仍然是可能的.[5]
- Java 更容易泄漏非内存资源, 而 C++ 的惯用做法更不会导致这种泄漏.
库[编辑]
- C++ 对于许多平台相关的特性提供了跨平台的访问方式. 从 Java 到原生的操作系统和硬件相关的函数的直接访问需要用到JNI(en:Java Native Interface).
运行时[编辑]
- C++ 通常来说会直接被编译成机器码,被操作系统直接执行. Java 通常会被编译成字节码,被Java虚拟机和解释器或者即时编译器编译成机器码然后执行.
- 因为表达方式不受限制,低级的 C++ 语言特性(例如:不被检查的数组访问,原始指针,类型双关语(en:type punning))不能在编译期间或者运行期间可靠地被检查. 相关的编程错误会导致低级的缓存溢出和段错误(en:segmentation fault). 标准模板库 提供了高级的抽象(例如 vector,list 和 map)来帮助避免这样的错误. 在 Java 里, 低级错误不会发生或者会被JVM检测到并以异常的形式报告给应用.
- Java 语言在越界访问数组的时候一般来说会对数组进行边界检查(en:bounds checking). 这消除了导致程序不稳定的一个可能因素,但这是以执行速度更慢一些作为代价的. 在一些情况下,编译器分析(en:compiler analysis)可以检测到不必要的边界检查并去掉. C++ 对于原生数组的越界访问没有要求特定的处理, 所以需要对于原生数组确认不越界. 但C++ 标准库里的一部分库象 std::vector 也提供了可选的边界检查. 总的来说, Java 数组是"总是安全;严格限制;开销较多" ,而 C++ 原生数组是"可选的开销; 完全不限制; 有潜在的不安全."
模板 vs. 泛型[编辑]
C++ 和 Java 都提供泛型编程的能力,分别是模板 和 泛型(en:Generics in Java). 虽然它们被创造用来解决类似的问题,有类似的语法,但实际上很不一样.
-
C++ 模板 Java 泛型 类和函数都可以使用模板. 类和方法都可以使用泛型. 参数可以是任意类型或者整型. 参数只能是能被引用的类型(非基本类型). 在编译的时候对于每种类型生成类或者函数的拷贝. 对于所有类型的参数,只有一个版本的类或者函数生成. 同一个类用不同类型生成的对象在运行期也是不同类型的 编译完成以后类型参数的类型是被消除的; 同一个类用不同类型参数生成的对象在运行期是相同类型的. 想要用到模板类或者函数的实现代码的话必须 include 它(只是声明是不够的). 有一个编译好的类文件里的类或者函数的签名就足以使用泛型了 模板可以被具体化 -- 可以为某个特定的模板参数提供单独的实现. 泛型不能被具体化. 模板参数可以有缺省参数(en:default argument)(只针对对于模板类,模板函数是没有此特性的). 泛型类参数无法拥有缺省参数. 不支持通配符. 返回的类型经常是嵌套的 typedef 形式的. 如果只用一次,那么支持通配符作为类型参数. 不直接支持设置类型参数的边界 (即, 不允许说明类型参数必须为某个类型的子类/父类), 但超编程提供了这个特性[6] 支持类型参数边界, 分别以 "extends" 和 "super" 来定义上界和下界; 同时允许定义类型参数之间的继承关系 允许生成有参模板的类的实例 (如 foo = new Foo<T>, T 为参数) 不允许生成有参模板类的实例 (除非使用反射) 模板类的类型参数可以用在 static 方法和变量上. 泛型类的类型参数无法用在 static 方法和变量上. static 变量不在在不同的类型参数生成的类之间共享. static 变量在不同类型参数生成的类的对象之间是共享的. 泛型类和函数在声明时不强制类参数的类限制. 使用错误的类参数会导致模板代码"不工作". 值得注意的是, 如果使用了错误的参数, 则错误信息将出现在定义模板的代码处 (而非调用模板的代码处), 说明 "不支持以该类型作为参数来实例化模板". 这种错误信息往往难以帮助人们找出真正的问题所在 (编程时究竟使用了何种 "错误的" 参数). 因此, 模板类或者函数的正确使用更依赖于正确的文档. 超编程以额外的代价提供了这些特性. 泛型类和函数在声明的时候强制了类参数的类限制(Generic classes and functions can enforce type relationships for type parameters in their declaration). 使用一个错误的参数会在使用它的时候导致一个类错误. 在泛型代码里操作和参数化类型只能按声明的时候保证安全的方式来使用. 这用失去弹性的代价来换取好得多的类型方面的安全性. 模板是图灵完全的 (参见 模板超编程). 泛型不是图灵完全的.
杂项[编辑]
- Java 和 C++ 在使代码在不同的文件分开方面使用了不同的技术. Java 使用了一个包系统,这个系统对所有的程序都要指定了文件名和路径. 在 Java 里, 编译器负责导入可执行的类文件. C++ 使用了头文件源代码的包含系统来在不同的文件共享声明.
- 编译好的 Java 代码一般来说比 C++ 文件小,因为Java字节码(en:Java bytecode)一般来说比机器码要更紧凑[来源请求],Java 程序都不是静态链接的.
- C++ 编译多了一个文本预处理过程, Java 是没有的. 因此一些使用者在他们的编译过程之前增加了一个预处理的过程,这样能更好的支持需要条件编译的情况.
- 两个语言里数组都是定长的. 在 Java 里, 数组是一等对象(en:first-class object), 而在 C++ 里它们只是它们的基本类型元素的连续的序列, 经常用一个指向第一个元素的指针和一个可选的长度来引用. 在 Java 里, 数组是被边界检查的,而且知道它们的长度, 而在 C++ 里你可以将任意的序列当成一个数组. C++ 和 Java 都提供了相关的容器类(分别为std::vector 和 java.util.ArrayList),可以改变大小.
- Java 的除法和模除操作符是定义成零截断的. C++ 没有定义这两个操作符是零截断的还是"负无穷截断"的. 在 Java 里-3/2 总是得到 -1, 但一个 C++ 编译器可能会返回 -1 或 -2, 视平台而定. C99 定义了和 Java 一样的除法方式. 两种语言都保证对于所有的 a 和 b(b!=0)(当 a 和 b都是整型的时候)
(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] | aload_1 iload_2 |
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 的表现好很多。
http://zh.wikipedia.org/wiki/Java%E5%92%8CC%2B%2B%E7%9A%84%E5%B0%8D%E7%85%A7