深入了解 C++ 与 Java

已发表: 2022-07-22

无数文章比较了 C++ 和 Java 的技术特性,但哪些差异最需要考虑? 例如,当比较显示 Java 不支持多重继承而 C++ 支持时,这是什么意思? 这是一件好事吗? 一些人认为这是 Java 的一个优势,而另一些人则认为这是一个问题。

让我们探讨一下开发人员应该选择 C++、Java 或其他语言的情况,更重要的是,为什么这个决定很重要。

检查基础:语言构建和生态系统

C++ 于 1985 年作为 C 编译器的前端推出,类似于 TypeScript 编译为 JavaScript 的方式。 现代 C++ 编译器通常编译为本机机器代码。 尽管有些人声称 C++ 的编译器降低了它的可移植性,并且它们确实需要为新的目标架构重新构建,但 C++ 代码几乎可以在每个处理器平台上运行。

Java 于 1995 年首次发布,并不直接构建为本机代码。 相反,Java 构建字节码,一种在 Java 虚拟机 (JVM) 上运行的中间二进制表示。 换句话说,Java 编译器的输出需要特定于平台的本机可执行文件才能运行。

C++ 和 Java 都属于类 C 语言,因为它们的语法通常类似于 C。 最显着的区别在于它们的生态系统:虽然 C++ 可以无缝调用基于 C 或 C++ 或操作系统 API 的库,但 Java 最适合基于 Java 的库。 您可以使用 Java 本机接口 (JNI) API 访问 Java 中的 C 库,但它容易出错并且需要一些 C 或 C++ 代码。 C++ 也比 Java 更容易与硬件交互,因为 C++ 是一种低级语言。

详细权衡:泛型、内存等

我们可以从多个角度将 C++ 与 Java 进行比较。 在某些情况下,C++ 和 Java 之间的决定是明确的。 除非应用程序是游戏,否则原生 Android 应用程序通常应该使用 Java。 大多数游戏开发者应该选择 C++ 或其他语言以获得尽可能流畅的实时动画; Java 的内存管理经常会导致游戏过程中出现延迟。

非游戏的跨平台应用程序超出了本次讨论的范围。 在这种情况下,C++ 和 Java 都不是理想的,因为它们对于高效的 GUI 开发来说过于冗长。 对于高性能应用程序,最好创建 C++ 模块来完成繁重的工作,并为 GUI 使用更具开发人员生产力的语言。

非游戏的跨平台应用程序超出了本次讨论的范围。 在这种情况下,C++ 和 Java 都不是理想的,因为它们对于高效的 GUI 开发来说过于冗长。

鸣叫

对于某些项目,选择可能不清楚,所以让我们进一步比较:

特征C++ 爪哇
适合初学者是的
运行时性能最好的好的
潜伏可预见不可预料的
引用计数智能指针是的
全局标记和清除垃圾收集必需的
堆栈内存分配是的
编译为本机可执行文件是的
编译成 Java 字节码是的
与低级操作系统 API 直接交互是的需要 C 代码
与 C 库的直接交互是的需要 C 代码
与 Java 库直接交互通过 JNI 是的
标准化的构建和包管理马文


除了表中比较的特性外,我们还将关注面向对象编程 (OOP) 特性,如多重继承、泛型/模板和反射。 请注意,两种语言都支持 OOP:Java 强制要求它,而 C++ 支持 OOP 以及全局函数和静态数据。

多重继承

在 OOP 中,继承是指子类从父类继承属性和方法。 一个标准示例是继承自更通用的Shape类的Rectangle类:

 // Note that we are in a C++ file class Shape { // Position int x, y; public: // The child class must override this pure virtual function virtual void draw() = 0; }; class Rectangle: public Shape { // Width and height int w, h; public: void draw(); };

多重继承是指子类从多个父类继承。 下面是一个示例,使用了RectangleShape类以及一个附加的Clickable类:

 // Not recommended class Shape {...}; class Rectangle: public Shape {...}; class Clickable { int xClick, yClick; public: virtual void click() = 0; }; class ClickableRectangle: public Rectangle, public Clickable { void click(); };

在这种情况下,我们有两种基本类型: ShapeRectangle的基本类型)和ClickableClickableRectangle继承自两者以组成两种对象类型。

C++支持多重继承; Java 没有。 多重继承在某些边缘情况下很有用,例如:

  • 创建高级领域特定语言 (DSL)。
  • 在编译时执行复杂的计算。
  • 以 Java 中根本不可能的方式提高项目类型的安全性。

但是,通常不鼓励使用多重继承。 除非与模板元编程结合使用,否则它会使代码复杂化并影响性能——最好只有最有经验的 C++ 程序员才能做到这一点。

泛型和模板

适用于任何数据类型的类的通用版本对于代码重用是实用的。 两种语言都提供这种支持——Java 通过泛型,C++ 通过模板——但 C++ 模板的灵活性可以使高级编程更安全、更健壮。 每次您在模板中使用不同类型时,C++ 编译器都会创建新的自定义类或函数。 此外,C++模板可以根据顶层函数的参数类型调用自定义函数,允许特定数据类型有专门的代码。 这称为模板特化。 Java 没有等效的功能。

相反,当使用泛型时,Java 编译器通过称为类型擦除的过程创建没有类型的通用对象。 Java 在编译期间执行类型检查,但程序员不能根据其类型参数修改泛型类或方法的行为。 为了更好地理解这一点,让我们看一个使用 C++ 模板template<class T1, class T2>的通用std::string format(std::string fmt, T1 item1, T2 item2)函数的快速示例我创建的库:

 std::string firstParameter = "A string"; int secondParameter = 123; // Format printed output as an eight-character-wide string and a hexadecimal value format("%8s %x", firstParameter, secondParameter); // Format printed output as two eight-character-wide strings format("%8s %8s", firstParameter, secondParameter);

C++ 将生成format函数为std::string format(std::string fmt, std::string item1, int item2) ,而 Java 将在没有item1item2的特定stringint对象类型的情况下创建它。 在这种情况下,我们的 C++ 模板知道最后一个传入参数是一个int ,因此可以在第二个format调用中执行必要的std::to_string转换。 如果没有模板,C++ printf语句试图将数字打印为字符串,就像在第二种format调用中一样,将具有未定义的行为,并可能使应用程序崩溃或打印垃圾。 Java 函数在第一次format调用中只能将数字视为字符串,而不能直接将其格式化为十六进制整数。 这是一个简单的例子,但它展示了 C++ 选择一个专门的模板来处理任意类对象而不修改其类或format函数的能力。 我们可以使用反射而不是泛型在 Java 中正确生成输出,尽管这种方法可扩展性较差且更容易出错。

反射

在 Java 中,可以(在运行时)找出结构细节,例如在类或类类型中哪些成员可用。 这个特性被称为反射,大概是因为它就像举起一面镜子对着物体看里面有什么。 (更多信息可以在 Oracle 的反射文档中找到。)

C++ 没有完全反射,但现代 C++ 提供运行时类型信息 (RTTI)。 RTTI 允许对特定对象类型进行运行时检测,但它无法访问对象成员等信息。

内存管理

C++ 和 Java 之间的另一个关键区别是内存管理,它有两种主要方法:手动,开发人员必须手动跟踪和释放内存; 和自动的,其中软件跟踪哪些对象仍在使用以回收未使用的内存。 在 Java 中,垃圾收集就是一个例子。

Java 需要垃圾收集内存,提供比手动方法更容易的内存管理,并消除通常会导致安全漏洞的内存释放错误。 C++ 本身不提供自动内存管理,但它支持一种称为智能指针的垃圾回收形式。 智能指针使用引用计数,如果使用正确,是安全且高性能的。 C++ 还提供了在对象销毁时清理或释放资源的析构函数。

虽然 Java 仅提供堆分配,但 C++ 支持堆分配(使用newdelete或较旧的 C malloc函数)和堆栈分配。 堆栈分配比堆分配更快更安全,因为堆栈是线性数据结构,而堆是基于树的,因此堆栈内存的分配和释放要简单得多。

C++ 与堆栈分配相关的另一个优点是一种称为资源获取即初始化 (RAII) 的编程技术。 在 RAII 中,引用等资源与其控制对象的生命周期相关联; 资源将在该对象的生命周期结束时被销毁。 RAII 是 C++ 智能指针在没有手动取消引用的情况下工作的方式——在函数顶部引用的智能指针在退出函数时会自动取消引用。 如果这是对智能指针的最后一次引用,则连接的内存也会被释放。 尽管 Java 提供了类似的模式,但它比 C++ 的 RAII 更尴尬,尤其是当您需要在同一个代码块中创建多个资源时。

运行时性能

Java 具有可靠的运行时性能,但 C++ 仍然占据桂冠,因为手动内存管理比实际应用程序的垃圾收集更快。 尽管由于 JIT 编译,Java 在某些极端情况下可以胜过 C++,但 C++ 赢得了大多数非平凡的情况。

特别是,与 C++ 减少堆分配的使用相比,Java 的标准内存库的分配使垃圾收集器过度工作。 但是,Java 仍然相对较快,并且应该可以接受,除非延迟是最重要的问题——例如,在具有实时限制的游戏或应用程序中。

构建和包管理

Java 在性能上的不足之处在于它的易用性弥补了这一点。 影响开发人员效率的一个组件是构建和包管理——我们如何构建项目并将外部依赖项引入应用程序。 在 Java 中,一个名为 Maven 的工具将这个过程简化为几个简单的步骤,并与 IntelliJ IDEA 等许多 IDE 集成。

然而,在 C++ 中,不存在标准化的包存储库。 甚至没有在应用程序中构建 C++ 代码的标准化方法:一些开发人员更喜欢 Visual Studio,而另一些开发人员则使用 CMake 或其他自定义工具集。 进一步增加了复杂性,某些商业 C++ 库是二进制格式的,并且没有一致的方法将这些库集成到构建过程中。 此外,构建设置或编译器版本的变化可能会给二进制库的工作带来挑战。

初学者友好

构建和包管理的摩擦并不是 C++ 远不如 Java 对初学者友好的唯一原因。 程序员可能难以安全地调试和使用 C++,除非他们熟悉 C、汇编语言或计算机的低级工作。 把 C++ 想象成一个强大的工具:它可以完成很多事情,但如果滥用就会很危险。

Java 的上述内存管理方法也使其比 C++ 更易于访问。 Java 程序员不必担心释放对象内存,因为该语言会自动处理这些问题。

决策时间:C++ 还是 Java?

一个流程图,左上角有一个深蓝色的“开始”气泡,最终通过一系列带有深蓝色分支的白色决策连接点连接到其下方的七个浅蓝色结论框之一,表示“是”和其他选项,浅蓝色的树枝代表“不”。第一个是“跨平台的 GUI 应用程序?” “是”表示结论,“选择一个跨平台的开发环境并使用它的主要语言”。 “否”指向“原生 Android 应用程序?”从“是”指向第二个问题,“它是游戏吗?”在第二个问题中,“否”指向结论“使用 Java(或 Kotlin)”,“是”指向不同的结论“选择跨平台游戏引擎并使用其推荐的语言”。来自“原生 Android 应用?”问题,“否”指向“本机 Windows 应用程序?”从“是”指向第二个问题,“它是游戏吗?”在第二个问题中,“是”指向结论,“选择一个跨平台游戏引擎并使用其推荐的语言”,而“否”指向不同的结论,“选择一个 Windows GUI 环境并使用它的主要语言(通常是 C++ 或 C#)。”来自“本机 Windows 应用程序?”问题,“否”指向“服务器应用程序?”从哪个“是”指向次要问题“开发人员类型?”从第二个问题来看,“中等技能”决定指向结论“使用 Java(或 C# 或 TypeScript)”,“熟练”决定指向第三个问题“最高优先级?”从第三个问题来看,“开发人员生产力”决定指向结论“使用 Java(或 C# 或 TypeScript)”,而“性能”决定指向不同的结论“使用 C++(或 Rust)”。从“服务器应用程序?”问题,“否”指向第二个问题,“驱动程序开发?”在第二个问题中,“是”指向结论“使用 C++(或 Rust)”,“否”指向第三个问题“物联网开发?”从第三个问题,“是”指向结论,“使用 C++(或 Rust)”,“否”指向第四个问题,“高速交易?”在第四个问题中,“是”指向结论,“使用 C++(或 Rust)”,“否”指向最后剩下的结论,“询问熟悉您的目标域的人”。
为各种项目类型选择最佳语言的扩展指南。

现在我们已经深入探讨了 C++ 和 Java 之间的差异,我们回到我们最初的问题:C++ 还是 Java? 即使对这两种语言有深入的了解,也没有一刀切的答案。

不熟悉低级编程概念的软件工程师在将决定限制为 C++ 或 Java 时可能会更好地选择 Java,但游戏等实时环境除外。 另一方面,希望扩展视野的开发人员可能会通过选择 C++ 了解更多信息。

但是,C++ 和 Java 之间的技术差异可能只是决定的一个小因素。 某些类型的产品需要特定的选择。 如果您仍然不确定,您可以查阅流程图——但请记住,它最终可能会将您指向第三种语言。