C++ 编程 - 第 1 章
C++ (发音为 "see plus plus") 是一种 通用 的,多范式 的,静态类型 的,自由格式 的 编程语言,支持过程式、面向对象、泛型和(最近)函数式编程范式,并且以其在代码中实现低成本抽象而闻名。如果你对上述任何概念不熟悉,请不要担心,我们将在后面的章节中介绍它们。
在 1990 年代,C++ 逐渐成为最流行的计算机编程语言之一,根据 TIOBE 指数,它仍然是第四大流行语言。[1] C++ 最初的设计重点是 系统编程,但其功能也使其成为创建最终用户应用程序的吸引人的语言,尤其是那些资源受限或需要非常高的 性能 的应用程序。C++ 广泛用于游戏开发、Web 客户端/服务器端、金融应用程序的后端和机器人技术。
Bjarne Stroustrup,来自 贝尔实验室 的计算机科学家,是 1980 年代 C++(最初名为“带类的 C”)的设计者和最初的实现者,它是对 C 编程语言 的增强。C,它也是在贝尔实验室为了实现 Unix 操作系统而由 丹尼斯·里奇 创建的,它让用户能够以比 汇编语言 (ASM) 更高的概念层次控制硬件,但表达能力仍然有限。Stroustrup 决定将 面向对象 的 Simula 语言中的程序组织功能与 C 的高效硬件资源利用能力结合起来。增强从添加 类 和 虚函数 等 命名空间、运算符重载、模板 和 异常处理 等许多功能开始,这些以及其他功能将在本书中详细介绍。C++ 的几个功能后来被 C 采用,包括用于创建程序中不可变值的 const
关键字,inline
函数,在 for 循环
中的声明,以及 C++ 风格的注释(使用//符号)。
C++ 编程语言 是一种由 ANSI(美国国家标准协会)、BSI(英国标准协会)、DIN(德国国家标准化机构)以及其他几个国家标准化机构认可的标准,并于 1998 年由 ISO(国际标准化组织)批准为 ISO/IEC 14882:1998,尽管更常被称为 C++98 或简称 C++。该标准包含两个部分:核心语言和标准库;后者包括 标准模板库 和 标准 C 库(ANSI C 89)。
2003 年版本,ISO/IEC 14882:2003,被称为 C++03,重新定义了标准语言作为一个单一项目。STL(“标准模板库”)早于 C++ 的标准化(最初是在 Ada 中实现的)成为标准的组成部分,并且是符合标准的实现的必要条件。
从 2004 年开始,标准委员会(包括 Bjarne Stroustrup)制定了新标准修订版细节,C++11(以前称为 C++0x)于 2011 年 8 月 12 日获得批准。C++11 使语言更加高效、易于使用,并且为标准库添加了更多功能。C++14 的规范于 2014 年 12 月 15 日发布,与 C++11 相比,其更改较小,并且对该标准的编译器支持也很快跟进。一些 表格 提供了所谓 现代 C++ 功能的编译器支持情况。
许多其他 C++ 库并不属于标准,一个流行的例子是 Boost。此外,用 C 编写的非标准库通常可以被 C++ 程序使用。
- C++ 源代码示例
// 'Hello World!' program
#include <iostream>
int main()
{
std::cout << "Hello World!" << std::endl;
return 0;
}
传统上,人们在学习一门新语言时编写的第一个程序被称为“Hello World”,因为它只做一件事,就是简单地显示“Hello World”这句话,同时在这个过程中揭示有关该语言的基本信息。 Hello World 解释(在 示例附录 中)提供了对这段代码的详细解释,其中可以看出这里提到的 C++ 的几个元素,包括类似 C 的语法和标准库的使用。
在你开始学习如何使用C++编写程序之前,了解一些你可能会遇到的关键概念非常重要。这些概念并不局限于C++,但对理解计算机编程总体来说很有帮助。有其他编程语言经验的读者可能想快速浏览一下本节,或直接跳过。
如今,有许多不同类型的程序在使用。从你用来确保所有程序正常运行的操作系统,到你用来娱乐的电子游戏和音乐应用程序,程序可以完成许多不同的目的。所有程序(也称为软件或应用程序)的共同点是,它们都由一系列指令组成,这些指令以某种形式用某种编程语言编写。这些指令告诉计算机做什么,以及通常如何去做。程序可以包含任何内容,从解决数学问题的指令,到在游戏中射击游戏角色时应该如何表现的指令。计算机将按照程序的指令,从头到尾,一次执行一条指令。
所有计算机程序(或者大多数程序)的另一个共同点是,它们解决问题并执行任务。向世界问好。在屏幕上绘制一个按钮。计算 26*78。驾驶汽车。幸运的是,计算机必须学习如何执行这些任务。换句话说,它们必须被编程。
为什么不呢?这是决定学习任何事物的最明确方法。虽然学习总是好的,但选择学习什么更重要,因为这将是你优先考虑的任务。这个问题的另一方面是,你将投入一些时间来获得新的技能。你必须决定这将如何使你受益。检查你的目标,比较类似的项目,或看看编程市场需要什么。无论如何,你懂得的编程语言越多越好。
C++不是理想的第一语言。但是,如果你愿意对C++投入不止一时的兴趣,那么你甚至可以将其作为你的第一语言来学习。确保花一些时间了解不同的范式,以及为什么 C++ 是一种多范式或混合语言。
如果你只是为了在你的履历上添上另一笔而学习,也就是说,你只愿意付出足够的努力来理解它的主要怪癖,并了解它的一些黑暗角落,那么你最好先学习另外两种语言。这将阐明C++在编程方法上有哪些特别之处。你应该选择一种命令式语言和一种面向对象语言。C 可能是前者的最佳选择,因为它具有良好的市场价值,并且与C++有直接的关系,尽管 ASM 也是一个不错的替代品。对于后者,Java 是一个不错的选择,主要是因为它与 C++ 共享很多语法,但它不支持命令式编程。阅读语言比较部分,以更好地理解它们之间的关系。
虽然学习 C 不是理解C++的必要条件,但你必须知道如何使用命令式语言。C++不会让你很容易理解一些更深层的概念,因为在C++中,你作为程序员,拥有更大的自由度。在 C++ 中,有许多方法可以做同一件事。理解选择哪些选项将成为掌握这种语言的基石。
如果你仅仅对学习面向对象编程感兴趣,你不应该学习C++。C++ 对对象提供了一些支持,但它仍然不是真正面向对象的,因此使用的术语和解决问题的思路将使其更难学习和掌握这些概念。如果你真的对面向对象编程感兴趣,你应该学习Smalltalk。
与所有语言一样,C++有其特定的应用范围,它可以在其中真正发光。C++ 比 C 和 Java 更难学习,但比两者都更强大。C++使你能够从在 C 或其他低级语言中必须处理的小事中抽象出来,但会赋予你比 Java 更多的控制权和责任。由于它不会提供类似高级语言中可以获得的默认功能,你将不得不搜索和检查这些功能的几个外部实现,并自由选择最适合你目的的实现(或者实现你自己的解决方案)。
从最基本的层面上来说,"编程语言"是人(程序员)和计算机之间的一种沟通方式。程序员使用这种沟通方式来向计算机发出指令。这些指令称为“程序”。
就像我们用来互相沟通的许多自然语言一样,程序员可以使用许多语言来与计算机沟通。每种编程语言都有自己的一套词语和规则,称为该语言的语法。如果你要编写程序,你必须遵循你所使用的语言的语法,否则你就无法被理解。
编程语言通常可以分为两类:低级和高级,这两个概念都将介绍给你,以及它们与 C++ 的关系。
计算机“语言”中较低的级别是
机器码(也称为二进制)是低级语言的最低形式。机器码由一串 0 和 1 组成,它们组合在一起形成计算机可以执行的有意义的指令。如果你看一页二进制代码,就会明白为什么二进制代码永远不是编写程序的实用选择;什么样的人才能记住一堆 1 和 0 的字符串代表什么意思呢?
汇编语言(也称为 ASM),在从低级到高级的级别上,正好高于机器码。它是计算机执行的机器语言指令的人类可读翻译。例如,程序员不是通过二进制表示(0 和 1)来引用处理器指令,而是使用更易于记忆的(助记符)形式来引用这些指令。这些助记符通常是表示相应指令动作的字母的简短组合,例如“ADD”表示加法,“MOV”表示将值从一个地方移动到另一个地方。
你不需要理解汇编语言就能用 C++ 编程,但了解“幕后”发生了什么总是有帮助的。学习汇编语言也能让你作为程序员拥有更多控制权,并帮助你调试和理解代码。
由于大多数编程任务的大小和复杂性,使用高级语言格式的优点远远超过任何缺点,这些优点包括
- 高级程序结构:循环、函数和对象在低级语言中的可用性有限,因为它们的存在本身就被认为是“高级”功能;也就是说,每个结构元素必须进一步翻译成低级语言。
- 可移植性:高级程序可以在不同的计算机上运行,而无需或只需很少的修改。低级程序通常使用仅在某些处理器上可用的专用函数,并且必须重新编写才能在另一台计算机上运行。
- 易用性:在汇编语言中需要许多行代码才能完成的任务,可以用高级编程语言中的库中的几个函数调用来简化。例如,Java 是一种高级编程语言,它可以用大约五行代码绘制出一个功能齐全的窗口,而等效的汇编语言则至少需要四倍的代码量。
高级语言用更少的代码做更多的事情,虽然有时会损失性能,并且程序员的自由度会降低。它们还尝试使用英语单词的形式,这些单词可以被没有或几乎没有编程经验的普通人阅读和理解。用这些语言之一编写的程序有时被称为“人类可读代码”。一般来说,抽象使得学习编程语言更容易。
不过,没有一种编程语言是用什么人都能理解的自然语言(比如“纯英语”)编写的(尽管 BASIC 和 COBOL 接近,而且有人正在奥斯莫西安教团的纯英语编译器和集成开发环境中为此努力,该环境完全用纯英语编写,那么纯英语的定义就需要讨论了)。无论如何,由于编写程序语言(构造的和形式的语言)时需要对书面表达进行简化和控制,因此程序的文本有时被称为“代码”,更具体地说被称为“源代码”。这将在本书的代码部分中详细讨论。
需要记住的是,虽然有些词语(指令)是用英语写的(主要是为了方便),但使用的语言是不同的(通常有充分的理由,否则有人会创造一种新的编程语言),除此之外,上面段落的其余部分可能只有在你开始构建解析器、语言和编译器时才重要。语言级别越高,它就越努力地通过支持代码的可移植性和通过表达式和结构的复杂性增加来提高人类的可理解性,来解决对硬件(CPU、协处理器、寄存器数量等)的抽象问题。
请记住,这种分类方案正在不断发展。C++ 仍然被认为是一种高级语言,但随着新语言(Java、C#、Ruby 等)的出现,C++ 开始与 C 等低级语言归为一类。
由于计算机只能理解机器代码,因此人类可读的代码必须被解释或翻译成机器代码。
一个解释器是一个程序(通常用更低级的语言编写),它一次解释程序指令,将其转换为解释器执行时要执行的命令。通常每个指令包含一行文本或提供其他明确的方法来区分每个指令,并且每次运行程序时都必须重新解释程序。
一个编译器是一个程序,用于将源代码一次一条指令地翻译成机器代码。翻译成机器代码可能涉及将编译器理解的一条指令拆分为多个机器指令。这些指令只翻译一次,之后机器可以理解并直接执行这些指令,无论何时被指示执行。本书的编译器部分对 C++ 编译器进行了全面的分析。
用于指示计算机的工具可能不同,但是无论使用什么语句,几乎每种编程语言都将支持实现以下功能的结构
- 输入
- 输入是指从键盘、鼠标或有时是其他程序等设备获取信息的行为。
- 输出
- 输出与输入相反;它将信息提供给计算机显示器或其他显示设备或程序。
- 数学/算法
- 所有计算机处理器(计算机的大脑)都具有执行基本数学运算的能力,每种编程语言都以某种方式告诉它执行这些运算。
- 测试
- 测试涉及告诉计算机检查某个条件,并在该条件为真或假时执行某些操作。条件是编程中最重要的概念之一,所有语言都有一些测试条件的方法。
- 重复
- 重复执行某些操作,通常伴随一些变化。
本书的语句部分提供了对 C++ 语言结构的进一步分析和说明。
信不信由你,这就是全部。你使用过的每个程序,无论多么简单或复杂,都是由功能组成的,这些功能或多或少类似于这些功能。因此,描述计算机编程的一种方法是将一个大型复杂的任务分解成越来越小的子任务,直到最终每个子任务都足够简单,可以用这些功能之一来执行。
C++ 主要编译而不是解释(有一些 C++ 解释器),然后在稍后“执行”。虽然这看起来可能很复杂,但在后面你会看到它有多么容易。
正如我们在介绍 C++ 部分中看到的那样,C++ 是从 C 演变而来,增加了抽象级别(因此我们可以正确地说 C++ 的级别高于 C)。我们将学习这些差异的细节,在本书的编程范式部分中,对于那些已经了解其他语言的人来说,应该看看编程语言比较部分。
一个编程范式是基于不同概念的编程模型,它塑造了程序员设计、组织和编写程序的方式。一个多范式编程语言允许程序员选择特定的单一方法或混合不同编程范式的部分。C++ 作为一种多范式编程语言,支持使用过程式编程或面向对象编程的单一或混合方法,并将通用编程甚至函数式编程概念的利用混合在一起。
过程式编程可以定义为命令式编程的一种子类型,它是一种基于过程调用的编程范式,其中语句被结构化为过程(也称为子例程或函数)。过程调用是模块化的,受范围约束。过程式程序由一个或多个模块组成。每个模块由一个或多个子程序组成。模块可能包含过程、函数、子例程或方法,具体取决于编程语言。过程式程序可能包含多个级别或范围,子程序定义在其他子程序中。每个范围可以包含外部范围不可见的名称。
过程式编程相对于简单的顺序编程提供了许多好处,因为过程式代码
- 更易于阅读和维护
- 更灵活
- 有利于良好程序设计的实践
- 允许模块以代码库的形式再次使用。
类型是指计算机语言如何处理其变量,以及如何通过类型区分变量。变量是程序在执行期间使用的值。这些值可以改变;它们是变量,因此得名。静态类型通常会生成执行速度更快的编译代码。当编译器知道正在使用的确切类型时,它可以更轻松地生成执行正确操作的机器代码。在 C++ 中,变量需要在使用之前定义,以便编译器知道它们是什么类型,因此是静态类型的。不是静态类型的语言被称为动态类型语言。
静态类型通常在编译时更可靠地发现类型错误,从而提高编译程序的可靠性。简单地说,这意味着“圆形钉子不适合方形孔”,因此当类型导致歧义或不兼容使用时,编译器会报告它。然而,程序员对类型错误的普遍性和静态类型可以捕获多少错误存在争议。静态类型支持者认为,经过类型检查的程序更可靠,而动态类型支持者则指出已经证明可靠的动态代码和小型错误数据库。因此,静态类型的价值随着类型系统强度的增加而增加。
静态类型系统比约束较弱的语言结构更约束强大的语言结构。这使得强大的结构更难使用,并将选择“正确工具解决问题”的负担放在程序员的肩上,否则他们可能倾向于使用最强大的工具。选择过于强大的工具可能会导致额外的性能、可靠性或正确性问题,因为对可以从强大的语言结构中期望的属性存在理论限制。例如,不加选择地使用递归或全局变量可能会导致有据可查的不利影响。
静态类型允许构建库,这些库不太可能被其用户意外误用。这可以用作一种额外的机制来传达库开发者的意图。
类型检查是在编译时或运行时验证和强制类型约束的过程。编译时检查,也称为静态类型检查,是在编译程序时由编译器执行的。运行时检查,也称为动态类型检查,是在程序运行时由程序执行的。如果类型系统确保类型之间的转换必须是有效的或导致错误,则称编程语言为强类型。另一方面,弱类型语言不提供此类保证,并且通常允许类型之间的自动转换,这些转换可能没有有用的目的。C++处于两者之间,允许混合使用自动类型转换和程序员定义的转换,从而允许在解释一种类型为另一种类型时具有几乎完全的灵活性。将一个类型的变量或表达式转换为另一个类型称为类型转换。
面向对象编程
[edit | edit source]面向对象编程可以看作是过程式编程的扩展,其中程序由称为对象的各个单元的集合组成,这些单元具有不同的目的和功能,对实现的依赖有限或没有依赖。例如,汽车就像一个对象;它可以将你从 A 点带到 B 点,而无需知道汽车使用的是什么类型的发动机或发动机的运作方式。面向对象的语言通常提供一种方法来记录一个对象可以做什么和不能做什么,就像驾驶汽车的说明一样。
对象和类
[edit | edit source]一个对象由成员和方法组成。成员(也称为数据成员、特征、属性或特性)描述了对象。方法通常描述与特定对象关联的操作。将对象视为名词,其成员为描述该名词的形容词,其方法为该名词可以执行的动词。
例如,跑车是一个对象。它的一些成员可能是它的高度、重量、加速度和速度。对象的成员只保存关于该对象的数据。跑车的一些方法可能是“驾驶”、“停车”、“比赛”等等。方法只有与跑车相关联时才有意义,成员也是如此。
让我们构建跑车对象的“蓝图”被称为类。类不会告诉我们跑车的速度有多快,或者它的颜色是什么,但它确实告诉我们跑车将有一个代表速度和颜色的成员,并且它们将分别是数字和单词。该类还为我们规划了方法,告诉汽车如何停车和驾驶,但这些方法仅使用蓝图无法采取任何行动 - 它们需要一个对象才能产生影响。
C++ 中的类与 C 中的结构相同;区别在于类用户可以通过 private 选项隐藏数据。在 C++ 中,对象是类的实例,它被视为一个内置变量,该变量包含许多值。
封装
[edit | edit source]封装,即信息隐藏(来自用户)的原则,是隐藏类数据结构并允许通过公共接口更改数据的过程,在公共接口中,对传入值的有效性进行检查,因此它不仅允许在对象中隐藏数据,还允许隐藏行为。这可以防止接口的客户端依赖于将来可能更改的实现部分,从而允许更容易地进行这些更改,即在不更改客户端的情况下进行更改。在现代编程语言中,信息隐藏原则以多种方式体现出来,包括封装和多态性。
继承
[edit | edit source]继承描述了两种(或更多)类型或对象的类之间的关系,其中一种被称为另一种类型的“子类型”或“子类”;因此,“子类”对象被认为继承了父类的特征,从而允许共享功能。这使程序员可以重用或减少代码,并简化软件的开发和维护。
继承通常也被认为包括子类型化,其中一种类型的对象被定义为另一种类型的更专门的版本(见Liskov 替换原则),尽管非子类型化继承也是可能的。
继承通常通过描述由其继承关系创建的继承层次结构(也称为继承链)中的对象类来表示,这是一种树状结构。
例如,可以创建一个名为“Mammal”的变量类,它具有进食、繁殖等特征;然后定义一个子类型“Cat”,它继承了这些特征,而无需显式地对其进行编程,同时添加了“追逐老鼠”等新特征。这允许在不同类型的对象之间表达一次公共点并重复使用多次。
在 C++ 中,我们可以拥有与其他类相关的类(一个类可以通过使用更旧的、预先存在的类
来定义)。这导致了一种情况,即一个新类具有旧类的所有功能,并另外引入了自己的特定功能。我们这里指的是派生,即一个给定类是另一个类,而不是组合,其中一个给定类包含另一个类。
当我们讨论类(和结构)继承时,将进一步解释此 OOP 属性,具体请参阅本书的类继承部分。
如果想要同时使用多个完全正交的层次结构,例如允许“Cat”从“Cartoon character”和“Pet”以及“Mammal”继承,那么我们正在使用多重继承。
多重继承
[edit | edit source]多重继承是一个类可以继承两个或多个类(分别称为其基类、父类、祖先类或超类)属性的过程。
本书的C++ 类继承部分将更详细地介绍这一点。
多态性
[edit | edit source]多态性允许为多个相关但不同的目的重用一个名称。多态性的目的是允许使用一个名称来表示一个通用类。根据数据的类型,执行通用情况的特定实例。
多态性概念更广泛。只要我们使用两个具有相同名称但实现不同的函数,就会存在多态性。它们也可能在接口上有所不同,例如,通过接受不同的参数。在这种情况下,选择哪个函数由重载解析来完成,并在编译时执行,因此我们将其称为静态多态性。
动态多态性将在类部分中深入介绍,我们将讨论它在派生类中重新定义方法时的使用。
泛型编程
[edit | edit source]泛型编程或多态性是一种编程风格,强调允许一个值采用不同的类型的方法,只要保持某些约定,例如子类型和签名。简单来说,泛型编程基于找到高效算法的最抽象表示。模板普及了泛型的概念。模板允许在不考虑最终使用的类型的情况下编写代码。模板是在标准模板库 (STL)中定义的,泛型编程是在 C++ 中引入的。
自由格式
[edit | edit source]自由格式是指程序员如何编写代码。基本上,除了 C++ 的语义规则外,没有关于如何选择编写程序的规则。只要是合法的 C++,任何 C++ 程序都应该可以编译。
C++ 的自由格式特性被一些程序员用来(或滥用,取决于你的观点)编写混淆的 C++(故意写成难以理解的代码)。在合适的语境下,这也可能被视为精湛的技艺(非功能性但对语言的艺术性控制),但总的来说,混淆的使用只被视为一种源代码安全机制,确保源代码更刻意地难以被第三方分析、复制或使用。通过对编译器足够的了解,源代码也可以被设计为在编译后的形式中保留“水印”,从而允许追踪到原始的源代码。
不存在完美的语言。一切取决于资源(工具、人员,甚至可用时间)和目标。对于更广泛地了解其他语言及其演变,这个主题超出了本书的范围,还有许多其他作品可供参考,包括计算机编程维基教科书。
本节旨在为已有经验的人提供快速入门,帮助他们了解 C++ 语言的特殊特性,并展示其独特之处。
理想语言取决于具体问题。所有编程语言都是为了表达解决问题的算法的一般机制而设计的。换句话说,它是一种语言——而不是简单的表达式——因为它能够表达对多个特定问题的解决方案。
编程语言中泛化的程度各不相同。有领域特定语言 (DSL),例如正则表达式语法,专门为模式匹配和字符串操作问题而设计。也有通用编程语言,例如 C++。
最终,不存在完美的语言。有些语言比其他语言更适合特定类型的問題。每种语言都做出了权衡,在某些领域牺牲效率以换取其他领域的效率。此外,效率不仅意味着运行时性能,还包括开发时间、代码可维护性以及影响软件开发的其他因素。最佳语言取决于程序员的具体目标。
此外,在选择语言时,另一个非常实用的考虑因素是程序员可用的该语言的工具的数量和质量。无论理论上语言有多好,如果在目标平台上没有可靠的工具集,那么这种语言就不是最佳选择。
最优语言(在运行时性能方面)是机器码,但机器码(二进制)是编码时间效率最低的编程语言。使用高级语言编写大型系统非常复杂,而使用机器码则超出了人类的能力。在接下来的部分中,将比较 C++ 与其他密切相关的语言,如C、Java、C#、C++/CLI 和D。
上面的引言表明,目前没有哪种编程语言能够直接将概念或想法转化为有用的代码,但有一些解决方案可以帮助解决这个问题。我们将介绍计算机辅助软件工程 (CASE)工具的使用,这些工具将解决部分问题,但其使用需要规划,并具有一定的复杂性。
这些部分的意图不是为了推崇一种语言高于另一种语言;每种语言都有其适用性。有些在特定任务中更胜一筹,有些更易于学习,还有些仅为程序员提供更好的控制级别。这一切也可能取决于程序员对特定语言的控制级别。
在 C++ 中,垃圾回收是可选的,而不是必需的。在本书的垃圾回收部分,我们将深入探讨这个问题。
正如我们将在本书的资源获取即初始化 (RAII) 部分中看到的那样,RAII 可以用来为大多数问题提供更好的解决方案。当 finally
用于清理时,它必须在每次使用该类时由类的客户端编写(例如,fileClass 类的客户端必须在 try
/catch
/finally
块中进行 I/O,以便能够保证 fileClass 被关闭)。使用 RAII,fileClass 的析构函数可以保证这一点。现在,清理代码只需要编写一次——在 fileClass 的析构函数中;类的用户无需做任何事。
默认情况下,C++ 编译器通常会“修饰”函数的名称,以便于函数重载和泛型函数。在某些情况下,您需要访问在 C++ 编译器中没有创建的函数。为了实现这一点,您需要使用 extern
关键字来声明该函数为外部函数
extern "C" void LibraryFunction();
C 在 Bjarne Stroustrup 决定创建“更好的 C”时,基本上是 C++ 的核心语言。许多语法约定和规则仍然适用,因此我们可以说 C 是 C++ 的一个子集。大多数最新的 C++ 编译器也可以编译 C 代码,考虑到一些小的不兼容性,因为C99 和 C++ 2003 已经不再兼容。您也可以在C 编程维基教科书上查看有关 C 语言的更多信息。
1998 年由 ANSI 标准定义的 C++(有时称为 C++98)几乎是,但并不完全是 1989 年由第一个 ANSI 标准定义的 C 语言(称为 C89)的超集。C++ 不是严格超集的方式有很多,也就是说,并非所有有效的 C89 程序都是有效的 C++ 程序,但将 C 代码转换为有效的 C++ 代码的过程相当简单(避免使用保留字,通过强制转换绕过更严格的 C++ 类型检查,声明所有调用的函数,等等)。
1999 年,C 语言进行了修订,并添加了许多新功能。截至 2004 年,这些新功能中的大多数“C99”功能都未包含在 C++ 中。有些人(包括 Stroustrup 本人)认为,C99 带来的变化具有与 C++98 对 C89 的添加不同的哲学,因此,这些 C99 变化旨在增加 C 和 C++ 之间的兼容性。
语言的合并似乎已经成为一个死议题,因为 C 和 C++ 标准委员会之间的协调行动没有取得实际成果,可以说这两种语言开始分化。
一些差异是
- C++ 支持函数重载,这在 C 中没有,特别是在 C89 中(可以争论,根据函数重载的定义松紧程度,在一定程度上可以使用 C99 标准来模拟这些功能)。
- C++ 支持 继承 和 多态性。
- C++ 添加了关键字 class,但保留了 C 中的 struct,具有兼容的语义。
- C++ 支持对类成员的访问控制。
- C++ 通过使用 模板 支持泛型编程。
- C++ 使用自己的标准库扩展了 C89 标准库。
- C++ 和 C99 提供了不同的复数功能。
- C++ 具有 bool 和 wchar_t 作为基本类型,而在 C 中它们是类型定义。
- C++ 比较运算符返回 bool,而 C 返回 int。
- C++ 支持运算符重载。
- C++ 字符常量类型为 char,而 C 字符常量类型为 int。
- C++ 具有特定的 强制转换运算符 (
static_cast
,dynamic_cast
,const_cast
和reinterpret_cast
)。 - C++ 添加了 mutable 关键字来解决物理常量和逻辑常量之间不完全匹配的问题。
- C++ 使用引用扩展了类型系统。
- C++ 支持用户定义类型的 成员函数、构造函数 和 析构函数,以建立不变量并管理资源。
- C++ 通过 typeid 和
dynamic_cast
支持 运行时类型识别 (RTTI)。 - C++ 包含 异常处理。
- C++ 有std::vector作为其标准库的一部分,而不是 C 中的可变长度数组。
- C++ 将
sizeof
运算符视为编译时操作,而 C 允许它成为运行时操作。 - C++ 具有 new 和 delete 运算符,而 C 使用 malloc 和 free 库函数。
- C++ 支持面向对象编程,无需扩展。
- C++ 不需要使用宏,与 C 不同,C 使用宏进行谨慎的信息隐藏和抽象(这对 C 代码的可移植性尤为重要)。
- C++ 支持以 // 表示的行注释。(C99 开始正式支持这种注释系统,大多数编译器也支持它作为扩展)。
- C++
register
关键字的语义与 C 的实现不同。
选择 C 或 C++
[edit | edit source]通常会发现有人推荐使用 C 而不是 C++(反之亦然),或者抱怨这些语言的一些特性。通常情况下,没有决定性的理由来偏爱一种语言而不是另一种语言。大多数试图衡量程序员生产力与编程语言之间关系的科学研究表明 C 和 C++ 基本上是相同的。对于某些情况,C 可能是更好的选择,例如内核编程,如硬件驱动程序,或关系数据库,这些情况不适合面向对象编程。另一个考虑因素是,C 编译器更普遍,因此 C 程序可以在更多平台上运行。虽然这两种语言仍在不断发展,但任何添加的新功能仍然保持与旧代码的高度兼容性,这使得程序员可以自由地使用这些新结构。在项目中,根据程序员的熟练程度或项目的需求,通常会制定规则来限制语言某些部分的使用(例如 RTTI、异常或内部循环中的虚拟函数)。对于新硬件,首先支持低级语言也是很常见的。由于 C 比 C++ 更简单、更低级,因此更容易检查和遵守行业指南。C 的另一个好处是,程序员更容易进行低级优化,尽管大多数 C++ 编译器可以自动保证几乎完美的优化。
最终,由程序员选择最适合工作的工具。如果可用的程序员只知道 C,那么很难证明选择 C++ 来完成项目是合理的。即使在相反的情况下,人们可能期望 C++ 程序员生成功能 C 代码,但所需的思维方式和经验并不相同。同样的道理也适用于 C 程序员和 ASM。这是由于语言结构和历史演化之间存在密切的关系。
有人可能认为,使用 C++ 编译器的 C++ 子集与使用 C 相同,但实际上,根据使用的编译器,可能会产生略微不同的结果。Java 编程语言 和 C++ 具有许多共同特征。以下是两种语言的比较。有关 Java 的更深入了解,请参见 Java 编程 WikiBook。
Java
[edit | edit source]Java 最初是为了支持 网络计算 在 嵌入式系统 上而创建的。Java 的设计目的是极度 可移植、安全、多线程 和 分布式,而这些都不是 C++ 的设计目标。Java 的语法对于 C 程序员来说很熟悉,但没有直接与 C 保持兼容性。Java 的设计初衷也是比 C++ 更简单,但它仍在继续发展并超越这种简化。
在 1999 年到 2009 年的十年间,特别是在致力于企业解决方案的编程行业中,“基于咖啡”的语言(依赖于 Smalltalk 中熟悉的“虚拟机”)变得越来越流行。这是一种性能与生产力的权衡,在当时是非常合理的,因为计算能力和对简化和更简洁的语言的需求(不仅易于采用,而且学习曲线更低)都很重要。这两种语言之间有足够的相似之处,使得熟练的 C++ 程序员可以轻松地适应 Java,而 Java 在今天仍然比 C++ 更简单,而且在采用的范式方面也比 C++ 更一致。
然而,这种兴趣的转变已经减少,这主要归因于这两种语言的演变。C++ 和 Java 的演变已经弥合了两种语言的许多问题和局限性,如今的软件需求也更加分散和多样化。现在,我们对移动设备、数据中心和桌面计算有特定的需求,这使得编程语言选择成为一个更加重要的议题。
C++ 和 Java 之间的差异是
- C++ 解析比 Java 稍微复杂一些;例如,
Foo<1>(3);
如果 Foo 是一个变量,则是一系列比较,但如果 Foo 是一个类模板的名称,则会创建一个对象。 - C++ 允许
命名空间
级别的常量、变量和函数。所有这样的 Java 声明必须在类或 接口 中。 - C++ 中的
const
表示数据为“只读”,并应用于类型。Java 中的final
表示变量不能重新分配。对于基本类型,例如const int
与final int
,它们是相同的,但对于复杂类,它们是不同的。 - C++ 在 C++11 标准之前不支持构造函数委托,而且只有最近的编译器才支持这种功能。
- C++ 生成在硬件上运行的机器代码,而 Java 生成在虚拟机上运行的字节码,因此使用 C++ 可以获得更高的性能,但代价是可移植性。
- C++,int main()本身就是一个函数,没有类。
- C++ 的访问限定符(public、private)使用标签和分组完成。
- C++ 默认情况下,对类成员的访问权限为private,而在 Java 中则是包访问权限。
- C++ 类声明以分号结尾。
- C++ 缺乏语言级别的垃圾回收支持,而 Java 内置了垃圾回收来处理内存释放。
- C++ 支持
goto
语句;Java 不支持,但它的 带标签的 break 和 带标签的 continue 语句提供了一些结构化的goto
功能。事实上,Java 强制实施 结构化控制流,目的是使代码更易于理解。 - C++ 提供了一些 Java 缺乏的底层功能。在 C++ 中,可以使用指针来操作特定的内存位置,这是编写底层 操作系统 组件所必需的任务。同样,许多 C++ 编译器支持 内联汇编器。在 Java 中,仍然可以通过 Java 本地接口 将汇编代码作为库访问。但是,每次调用都会产生很大的开销。
- C++ 允许在原生类型之间进行各种隐式转换,还允许程序员定义涉及复合类型的隐式转换。但是,Java 只允许原生类型之间的扩展转换是隐式的;任何其他转换都需要显式强制转换语法。C++11 禁止从初始化列表进行收缩转换。
- 其结果是,虽然 Java 和 C++ 中的循环条件(
if
、while
和for
中的退出条件)都期望一个布尔表达式,但在 Java 中,诸如if(a = 5)
的代码会导致编译错误,因为从 int 到 boolean 没有隐式收缩转换。如果代码是if(a == 5)
的拼写错误,这很方便,但当将诸如if (x)
的语句从 Java 翻译到 C++ 时,需要显式强制转换会增加冗余。
- 其结果是,虽然 Java 和 C++ 中的循环条件(
- 对于向函数传递参数,C++ 支持真正的 传值调用 和 传引用调用。与 C 相似,程序员可以使用传值参数和 间接寻址 来模拟按引用传递参数。在 Java 中,所有参数都是按值传递的,但对象(非基本)参数是 引用 值,这意味着 间接寻址 是内置的。
- 通常,Java 内置类型具有指定的大小和范围;而 C++ 类型具有各种可能的大小、范围和表示形式,这些表示形式甚至可能在同一编译器的不同版本之间发生变化,或者可以通过编译器开关进行配置。
- 特别是,Java 字符是 16 位 Unicode 字符,字符串由这些字符的序列组成。C++ 提供窄字符和宽字符,但每种字符的实际大小和使用的字符集都取决于平台。字符串可以由任何一种类型构成。
- C++ 中浮点数的值和运算的舍入和精度取决于平台。Java 提供了 严格浮点模型,该模型保证在不同平台上得到一致的结果,不过通常使用更宽松的操作模式以允许最佳的浮点性能。
- 在 C++ 中,指针 可以直接作为内存地址值进行操作。Java 没有指针——它只有对象引用和数组引用,它们都不能直接访问内存地址。在 C++ 中,可以构造指向指针的指针,而 Java 引用只能访问对象。
- 在 C++ 中,指针可以指向函数或成员函数(函数指针 或 函数对象)。Java 中等效的机制使用对象或接口引用。C++11 对函数对象有库支持。
- C++ 支持程序员定义的 运算符重载。Java 中唯一重载的运算符是 "
+
" 和 "+=
" 运算符,它们既可以连接字符串,也可以执行加法。 - Java 特色标准 API 支持 反射 和 动态加载 任意新代码。
- Java 有泛型。C++ 有模板。
- Java 和 C++ 都区分原生类型(也称为“基本”或“内置”类型)和用户定义类型(也称为“复合”类型)。在 Java 中,原生类型只有值语义,而复合类型只有引用语义。在 C++ 中,所有类型都有值语义,但可以为任何对象创建引用,这将允许通过引用语义操作该对象。
- C++ 支持任意类的 多重继承。Java 支持类型的多重继承,但只支持单一实现继承。在 Java 中,一个类只能从一个类派生,但一个类可以实现多个 接口。
- Java 明确区分接口和类。在 C++ 中,多重继承和纯虚函数使定义与 Java 接口功能相同的类成为可能。
- Java 同时提供语言和标准库支持 多线程。Java 中的
synchronized
关键字 提供了简单而安全的 互斥锁 来支持多线程应用程序。C++11 提供了类似的功能。虽然以前版本的 C++ 中的库提供了互斥锁机制,但缺乏语言语义使得编写 线程安全 代码更加困难且容易出错。
内存管理
[edit | edit source]- Java 需要自动 垃圾回收。C++ 中的内存管理通常由人工完成,或通过 智能指针 完成。C++ 标准允许垃圾回收,但不要求垃圾回收;在实践中很少使用垃圾回收。当允许重新定位对象时,现代垃圾回收器可以提高整体应用程序的空间和时间效率,而不是使用显式释放。
- C++ 可以分配任意大小的内存块。Java 只通过对象实例化分配内存。(请注意,在 Java 中,程序员可以通过创建字节数组来模拟任意内存块的分配。但是,Java 数组 是对象。)
- Java 和 C++ 使用不同的惯例来管理资源。Java 主要依赖于垃圾回收,而 C++ 主要依赖于 RAII(资源获取即初始化) 惯例。这反映在两种语言之间的一些差异。
- 在 C++ 中,通常将复合类型的对象分配为本地栈绑定变量,这些变量在它们 超出范围 时被销毁。在 Java 中,复合类型始终在堆上分配,并由垃圾回收器收集(除非在使用 逃逸分析 将堆分配转换为栈分配的虚拟机中)。
- C++ 有析构函数,而 Java 有 终结器。两者都在对象释放之前调用,但它们有很大不同。C++ 对象的析构函数必须隐式(在栈绑定变量的情况下)或显式调用以释放该对象。析构函数在程序中释放对象的那一点上 同步 执行。因此,C++ 中的同步、协调的取消初始化和释放满足 RAII 惯例。在 Java 中,对象释放由垃圾回收器隐式处理。Java 对象的终结器在该对象最后一次被访问之后和实际释放之前 异步 调用,这可能永远不会发生。很少有对象需要终结器;只有必须保证在释放之前清理对象状态的对象才需要终结器——通常是释放 JVM 外部资源。在 Java 中,使用 try/finally 结构来执行资源的安全同步释放。
- 在 C++ 中,可能存在悬垂指针 - 指向已被销毁对象的引用;尝试使用悬垂指针通常会导致程序崩溃。在 Java 中,垃圾回收器不会销毁被引用的对象。
- 在 C++ 中,可能存在已分配但无法访问的对象。一个无法访问的对象是指没有可访问引用指向它的对象。无法访问的对象无法被销毁(释放),会导致内存泄漏。相比之下,在 Java 中,一个对象只有在变得无法访问(由用户程序)时,才会被垃圾回收器释放。(注意:弱引用得到了支持,它与 Java 垃圾回收器协作,允许不同的可访问性等级。)Java 中的垃圾回收可以防止许多内存泄漏,但在某些情况下,内存泄漏仍然可能发生。
- C++ 标准库提供了一组有限的基本且相对通用的组件。Java 拥有一个更大的标准库。C++ 可以通过(通常是免费的)第三方库来获得这些额外的功能,但第三方库无法提供与标准库相同的无处不在的跨平台功能。
- C++ 在很大程度上与 C向后兼容,C 库(例如大多数操作系统的API)可以直接从 C++ 访问。在 Java 中,其标准库更丰富,提供对许多功能的跨平台访问,这些功能通常只能在特定平台的库中使用。从 Java 直接访问本机操作系统和硬件函数需要使用Java 本机接口。
- C++ 通常直接编译成机器码,然后由操作系统直接执行。Java 通常编译成字节码,然后由Java 虚拟机 (JVM) 进行解释或JIT 编译成机器码并执行。
- 由于 C++ 中某些语言特性使用缺乏约束(例如,未经检查的数组访问、原始指针),编程错误可能导致低级缓冲区溢出、页错误和段错误。标准模板库提供了更高层次的抽象(如 vector、list 和 map),有助于避免此类错误。在 Java 中,此类错误要么根本无法发生,要么会被JVM 检测到并以异常的形式报告给应用程序。
- 在 Java 中,对所有数组访问操作都隐式地执行边界检查。在 C++ 中,对本机数组的数组访问操作不会进行边界检查,并且对标准库集合(如 std::vector 和 std::deque)的随机访问元素访问的边界检查是可选的。
- Java 和 C++ 使用不同的技术将代码拆分成多个源文件。Java 使用包系统来规定所有程序定义的文件名和路径。在 Java 中,编译器导入可执行的类文件。C++ 使用头文件源代码包含系统,用于在源文件之间共享声明。
- C++ 中的模板和宏,包括标准库中的模板和宏,在编译后可能导致类似代码的重复。其次,与标准库的动态链接消除了在编译时绑定库。
- C++ 编译包含一个文本化的预处理阶段,而 Java 没有。Java 支持许多优化,可以减轻对预处理器的需求,但一些用户会在其构建过程中添加预处理阶段,以便更好地支持条件编译。
- 在 Java 中,数组是容器对象,您可以在任何时候检查其长度。在这两种语言中,数组的大小都是固定的。此外,C++ 程序员通常只通过指向其第一个元素的指针来引用数组,而无法从中检索数组大小。但是,C++ 和 Java 都提供了容器类(分别为 std::vector 和 java.util.ArrayList),这些容器类是可调整大小的,并存储其大小。C++11 的 std::array 提供了固定大小的数组,其效率与传统数组类似,具有返回大小的函数,以及可选的边界检查。
- Java 的除法和模运算符被定义为截断为零。C++ 没有指定这些运算符是截断为零还是“截断为负无穷大”。-3/2 在 Java 中始终为 -1,但 C++ 编译器可能会
返回
-1 或 -2,具体取决于平台。C99 定义的除法方式与 Java 相同。这两种语言都保证对于所有 a 和 b(b != 0),(a/b)*b + (a%b) == a
。C++ 版本有时会更快,因为它可以自由选择对处理器本机的任何截断模式。 - Java 中的整数类型大小是定义好的(int 为 32 位,long 为 64 位),而在 C++ 中,整数和指针的大小取决于编译器。因此,精心编写的 C++ 代码可以利用 64 位处理器的功能,同时仍然可以在 32 位处理器上正常运行。但是,没有考虑处理器字长编写的 C++ 程序可能在某些编译器上无法正常运行。相反,Java 中固定的整数大小意味着程序员无需关心不同的整数大小,并且程序会完全相同地运行。这可能会导致性能损失,因为 Java 代码无法使用任意处理器的字长运行。C++11 提供了 uint32_t 等类型,其大小得到保证,但编译器并非强制在没有对该大小提供本机支持的硬件上提供这些类型。
计算性能是指硬件和软件系统在执行计算工作(如算法或事务)时,资源消耗的度量。更高性能被定义为“使用更少的资源”。我们感兴趣的资源包括内存、带宽、持久存储和 CPU 周期。由于现代桌面和服务器系统上除 CPU 周期外,其他资源都非常充足,因此性能通常被视为指最少的 CPU 周期;这通常直接转化为最少的时间。比较两种软件语言的性能需要一个固定的硬件平台,以及(通常是相对的)两种或多种软件子系统的度量。本节将比较 C++ 和 Java 在 Windows 和 Linux 等常见操作系统上的相对计算性能。
早期的 Java 版本的性能明显落后于 C++ 等静态编译语言。这是因为这两种密切相关的语言的程序语句可能编译成少量机器指令(C++),而编译成更多涉及多个机器指令的字节码(由 Java JVM 解释)。例如
Java/C++ 语句 | C++ 生成的代码 | Java 生成的字节码 |
---|---|---|
vector[i]++; | mov edx,[ebp+4h] mov eax,[ebp+1Ch] |
aload_1 iload_2 |
虽然这在嵌入式系统中可能仍然是这种情况,因为需要占用很少的空间,但针对长时间运行的服务器和桌面 Java 进程的即时 (JIT) 编译器技术的进步已经缩小了性能差距,并在某些情况下甚至让 Java 取得了性能优势。实际上,Java 字节码在运行时被编译成机器指令,其方式类似于 C++ 的静态编译,从而产生类似的指令序列。
目前,C++ 在大多数操作中仍然比 Java 快,即使在低级和数值计算中也是如此。有关更深入的信息,您可以查看 Java 与 C++ 的性能对比。它有点偏向 Java,但非常详细。
C 和 C++ 程序员可能会对导入的工作方式感到困惑,反之,例如,Java 程序员可能会对 include 文件的正确使用感到困惑。在比较现代编程语言中的符号表 **导入** 与 **#includes** 的使用(例如 C 和 C++)时。虽然这两种技术都是解决同一问题的解决方案,即跨多个源文件进行编译,但它们是截然不同的技术。由于几乎所有现代编译器都包含本质上相同的编译阶段,因此最大的区别可以解释为:include 发生在编译的词法分析阶段,而导入则在语义分析阶段才进行。
导入的优势
- 导入不会重复任何词法分析工作,这通常会导致大型项目编译速度更快。
- 导入不需要将代码拆分为单独的文件进行声明/实现。
- 导入更利于对象代码的发布,而不是源代码。
- 导入允许源文件之间存在循环依赖关系。
- 导入隐式地带有一种机制,用于解决当多个符号表定义相同符号时发生的符号冲突。
导入的劣势
- 当可导入的模块被修改时,由于没有定义和实现的分离,所有依赖模块都必须重新编译,这在大型项目中可能会导致大量的编译时间。
- 导入需要一种在对象代码中定义符号表的标准机制。这种限制是否真正是一个弱点尚可商榷,因为标准符号表对于许多其他原因是有用的。
- 导入需要一种在编译时发现符号表的方法(例如 Java 中的类路径)。然而,当存在一种标准方法来完成此操作时,这并不一定比指定 include 文件的位置更复杂。
- 当允许循环依赖关系时,几个相互依赖的源文件的语义分析可能需要交错进行。
- 除非语言包含对部分类型的支持,否则使用导入而不是 include 的语言要求一个类的所有源代码都位于单个源文件中。
include 的优势
- 使用 include,源文件在语义分析阶段没有相互依赖关系。这意味着在这个阶段,每个源文件都可以作为一个独立的单元进行编译。
- 将定义和实现分离到头文件和源文件中,减少了依赖关系,并允许仅在实现细节发生更改时重新编译受影响的源文件,而无需重新编译其他文件。
- include 文件与其他预处理器功能结合使用,允许进行几乎任意的词法处理。
- 虽然这种做法并不普遍,但如果语言本身不支持某些现代语言特性(例如 Mixin 和方面),include 可以为这些特性提供基本的支持。
- include 不是底层语言语法的组成部分,而是预处理器语法的组成部分。这有一些缺点(需要学习另一种语言),但也有一些优点。预处理器语法,在某些情况下包括 include 文件本身,可以在几种不同的语言之间共享。
include 的劣势
- include 和必要的预处理器可能需要在编译的词法分析阶段进行更多遍的处理。
- 在大型项目中多次包含头文件的重复编译速度可能非常慢。然而,这可以通过使用预编译头文件来缓解。
- 对于初学者来说,正确使用头文件,尤其是全局变量的声明,可能很棘手。
- 由于 include 通常需要在源代码中指定包含文件的位置,因此环境变量经常需要提供 include 文件路径的一部分。更糟糕的是,这种功能在所有编译器中都没有以标准方式支持。
一个比较 C++ 和 Java 的示例 在这里。
C#
[edit | edit source]C#(发音为“See Sharp”)是一种多用途的计算机编程语言,使用 Microsoft .NET Framework 满足所有开发需求。
我们已经介绍了 Java。C# 非常相似,它采用 C++ 的基本运算符和风格,但强制程序类型安全,这意味着它在名为 虚拟机 的受控沙箱中执行代码。因此,所有代码都必须封装在对象中,以及其他方面。C# 提供了许多扩展,以促进与 微软 的 Windows、COM 和 Visual Basic 的交互。C# 是一个 ECMA 和 ISO 标准。
C# 是微软对 (当时由 Sun 开发的) Java 语言的回应,Java 语言开始对企业产生重大影响。在他们试图将 J++ 推向市场失败以及与 Sun 的法律纠纷之后,微软将注意力转向了托管语言,即使是作为一种保持 Visual Basic 相关性并拥有大量开发人员的方式,因此,随着 Windows “Longhorn” 项目 (后来成为 Windows Vista) 的宣布,推动了托管语言的发展及其与 Windows 操作系统的集成,并相信从此以后,“所有新的 Windows API 都将是托管的”。
然而,今天,微软似乎终于认识到,托管语言,即使考虑 Java 的采用,也缺乏开发操作系统的要求。微软甚至开始了一个基于 C# 的操作系统来测试前提,但意识到所有主要的软件项目,即使是 Windows 操作系统附带的实用程序,也大多是基于 C 或 C++ 的。即使托管代码仍然有其地位,C 和 C++ 最终被接受为未来可预见时期内软件行业的核心语言。在 Windows 中,这被称为 “C++ renaissance”,这是在营销机器将开发人员笼罩在黑暗时代之后。
- C# 和 C++ 之间的一些相似之处
- 它们都是 **面向对象** 的语言,这意味着它们使用类、继承和多态性(尽管语法不同)。这可能被认为是一个区别,因为 C# 被认为是一种纯粹的面向对象语言,而 C++ 支持各种其他 范式。
- C# 和 C++ 都是 **编译** 语言,这意味着源代码必须转换为二进制格式才能运行。
- C# 和 C++ 之间的一些区别
- C++ 编译成机器代码,而 C# 编译成 中间表示,并在 通用语言运行时 (CLR) 虚拟机上运行。
- C# 通常不使用指针,而在 C++ 中,指针的使用非常频繁。C# 仅在不安全模式下允许使用指针。
- C# 主要由 Windows 使用,这并不是最方便的,但 C++ 可以毫无问题地用于任何平台。
- C++ 可以创建独立应用程序,而 C# 则不能。
- C# 支持 foreach 循环,而 C++ 则不支持。
- C++ 支持多重继承,但 C# 不支持多重继承。
- 除了 private、public 和 protected 之外,C# 还有两个额外的修饰符,分别是 internal 和 protected internal。
- C++ 更常用于应用程序开发,因为它与硬件的直接交互和更好的性能要求,而 C# 编程主要用于性能不那么重要的 Web 和桌面应用程序。
- 与 C++ 相比,C# 的劣势
- 限制:使用 C#,诸如从类进行多重继承(C# 实现了一种不同的方法,称为多重实现,其中一个类可以实现多个接口)、在堆栈上声明对象、确定性销毁(允许 RAII)以及允许默认参数作为函数参数(在 C# 版本 < 4.0 中)的功能将不可用。
- 性能(速度和大小):与本机 C++ 相比,使用 C# 构建的应用程序可能性能不佳。C# 具有侵入式垃圾回收器、引用跟踪以及框架服务中的一些其他开销。仅 .NET 框架本身就具有很大的运行时占用空间 (~30 Mb 内存),并且需要安装几个版本的框架。
- 灵活性:由于依赖 .NET 框架,操作系统级功能(系统级 API)由一组通用的函数缓冲,这将减少一些自由度。
- 运行时重新分发:程序需要与 .NET 框架一起分发(预先 Windows XP 或非 Windows 机器),类似于 Java 语言的问题,并附带所有正常的升级要求。
- 可移植性:完整的 .NET 框架仅在 Windows 操作系统上可用,但有一些开源版本提供了大多数核心功能,并且也支持 GNU-Linux 操作系统,例如 MONO 和 Portable.NET http://www.gnu.org/software/dotgnu/pnet.html。例如,对于 C# 和 CLI 对 C++ 的扩展,存在 ECMA 和 ISO .NET 标准。
- 与 C++ 相比,C# 的优势
C++ 中有几个缺点在 C# 中得到了解决。
- 其中一个比较微妙的是使用引用变量作为函数参数。当代码维护人员查看 C++ 源代码时,如果调用函数在某个头文件中声明,则直接代码不会提供任何指示,表明函数的参数是作为非 const 引用传递的。按引用传递的参数可以在调用函数后更改,而按值传递的参数或作为 const 传递的参数则不能更改。不熟悉该函数的维护人员在寻找意外变量值变化的位置时,还需要检查函数的头文件,以确定该函数是否可能已更改了变量的值。C# 要求在函数调用中(除了函数声明之外)放置 **ref** 关键字,从而提示维护人员该值可能被函数更改。
- 另一个是内存管理,C# 在虚拟机中运行,该虚拟机具有处理内存管理的能力,但在 C++ 中,开发人员需要自己处理内存。C# 具有垃圾回收器,它会释放未使用的对象的指针指向的内存。
一个比较 C++ 和 C# 的示例可以在 这里找到。
托管 C++ (C++/CLI)
[edit | edit source]托管 C++ 是对托管扩展的 C++ 的简写,它是 .NET 框架 的一部分,来自 Microsoft。这种 C++ 语言扩展是为了添加诸如自动垃圾收集和堆管理、数组的自动初始化以及对多维数组的支持等功能而开发的,从而简化了在 C++ 中编程的所有那些细节,否则这些细节必须由程序员来完成。
托管 C++ 不会被编译成机器码。相反,它被编译成 通用中间语言,这是一种面向对象的机器语言,以前被称为 MSIL。
#include<iostream.h>
#include<math.h>
void main()
{
int choose;
double Area,Length,Width,Radius,Base,Height;
cout<<"circle(1)";
cout<<"Square(2)";
cout<<"Rectangle(3)";
cout<<"Triangle(4)";
cout<<"select 1,2,3,4:";
loop:
cin>>choose;
if(choose=='1')
{
double Radius;
const double pi=3.142;
cout<<"Enter Radius";
cin>>Radius;
Area=pi*pow(Radius,2);
}
else if(choose=='2')
{
double Length;
cout<<"Enter Length:";
cin>>Length;
Area= pow(1,2);
}
else if (choose=='3')
{
double Length,Width;
cout<<"Enter Length:";
cin>>Length;
cout<<"Enter Width:";
cin>>Width;
Area=Length*Width;
}
else if(choose=='4')
{
double Base,Height;
cout<<"Enter Base:";
cin>>Base;
cout<<"Enter Height:";
cin>>Height;
Area=Height*Base/2;
}
else
{
cout<<"Select only 1,2,3,4:";
goto loop;
}
cout<<"Area:"<<Area;
}
D
[edit | edit source]由 Digital Mars(一家小型美国软件公司,也以生产 C 编译器(随着时间的推移被称为 Datalight C 编译器、Zorland C 和 Zortech C)而闻名)内部开发的 D 编程语言,第一个用于 Windows 的 C++ 编译器(最初被称为 Zortech C++,后更名为 Symantec C++,现在是 Digital Mars C++ (DMC++))以及各种实用程序(例如支持 MFC 库的用于 Windows 的 IDE)。
该语言最初由 Walter Bright 设计,自 2006 年以来,它一直与 Andrei Alexandrescu 和其他贡献者合作。虽然 D 起源于 C++ 的重新设计,并且主要受其影响,但 D 并不是 C++ 的变体。D 重新设计了一些 C++ 特性,并受其他编程语言中使用的概念影响,例如 Java、C# 和 Eiffel。因此,D 是一种不断发展的开源系统编程语言,支持多种编程范式。
它支持过程式、泛型、函数式和面向对象的范式。最值得注意的是,它提供了功能强大但易于使用的编译时元编程功能。
它旨在提供效率、控制和建模能力与安全性以及程序员生产力的实用组合。它的另一个目标是易于初学者使用,并在有经验的程序员需要时提供高级功能。
支持的平台
[edit | edit source]D 在 Windows、Linux、OSX 和 FreeBSD 上的 x86 和 x86_64 上得到官方支持。其他平台(Android、iOS 和 Solaris)和硬件(ARM、MIPS 和 Power-PC)的支持正在开发中。
编译器
[edit | edit source]有 3 个生产就绪的编译器:DMD、GDC 和 LDC。
- DMD 是参考实现。另外两个编译器共享 DMD 的前端。它提供了非常快的编译速度,这对开发时间很有用。
- GDC 使用 GCC' 的后端进行代码生成。它与 GNU 工具链很好地集成。
- LDC 使用 LLVM' 的后端。它可以很好地与 LLVM 工具链的其他部分集成。
与 C 和 C++ 的接口
[edit | edit source]D 可以直接链接 C 和 C++ (*) 静态和共享库,无需任何包装器或额外开销(与 C 和 C++ 相比)。支持 C++ 平台特定 ABI 的子集(例如 GCC 和 MSVC)
- C++ 命名修饰约定,如命名空间、函数名和其他
- C++ 函数调用约定
- C++ 虚拟函数表布局,用于单继承
通常,D 在每个平台上使用平台链接器(ld.bfd、ld.gold 等,在 Linux 上),唯一的例外是 Windows,在 Windows 上,默认情况下使用 Optlink。MSVC link.exe 也受支持,但必须首先下载 Windows SDK。
D 中缺少 C 和 C++ 的功能
[edit | edit source]C/C++ 程序员会发现的一些新功能是
- 通过内省进行设计——可以设计一个模板类或结构体,以在编译时检查其模板参数的不同功能,然后适应这些功能。例如,可组合的分配器设计可以检查父分配器是否提供重新分配,并有效地委派给它,或者回退到使用 malloc() 和 free() 实现重新分配,或者根本不提供它。这样做在编译时的益处是,所述分配器的用户可以知道他是否应该使用 reallocate(),而不是得到神秘的运行时错误。
- 真正的模块
- 声明和导入的顺序(在 C++ 术语中为
#include
)无关紧要。无需预先声明任何内容。您可以重新排列内容,而不会改变含义。 - 更快的编译速度——C++ 的编译模型天生就 慢。此外,像 DMD 这样的编译器还有进一步的优化。
- 更强大的条件编译,无需预处理器。
pure
函数——无副作用的函数,允许进行内部变异。- 不可变性——保证声明为不可变的变量可以从多个线程安全地访问(无需锁定和竞争条件)。
- 契约式设计
- 通用函数调用语法 (UFCS)——允许像这样调用自由函数
void copyTo(T)(T[] src, T[] dst)
:sourceArray.copyTo(destinationArray)
- 内置单元测试
- 垃圾收集(可选)
scope
控制流语句(在 C++ 中使用ScopeGuard
惯用法部分模拟)。
一等公民
- 动态数组
int[] array; //declare empty array variable
array ~= 42; //append 42 to the array; array.equals([ 42 ]) == true
array.length = 5; //set the length to 5; will reallocate if needed
int[] other = new int[5]; // declare an array of five elements
other[] = 18; // fill the array with 18; other.equals([18, 18, 18, 18, 18]) == true
array[] = array[] * other[]; //array[i] becomes array[i] * other[i]
array[$ - 1] = -273; // set the last element to -273; when indexing an array the $ context variable is translated to array.length
int[] s = array[2 .. $]; // s points to the last 3 elements of array (no copying occurs).
- Unicode 字符串
string s1 = "Hello "; // array of immutable UTF8 chars
immutable(char)[] s2 = "World "; // `s2` has the same type as `s1`
string s3 = s1 ~ s2; // set `s3` to point to the result of concatenating `s1` with `s2`
char[] s4 = s3.dup; // `s4` points to the mutable array "Hello World "
s4[$-1] = '!'; // change the last character in the string
s4 ~= "<-> Здравей, свят!"; // append Cyrillic characters that don't fit in a single UTF-8 code-unit
import std.conv : to;
wstring ws = s4.to!wstring; //convert s4 to an array of immutable UTF16 chars
foreach (dchar character; ws) // iterate over ws; 'character' is an automatically transcoded UTF32 code-point
{
import std.stdio : writeln; // scoped selective imports
character.writeln(); //write each character on a new line
}
您可以在 dpaste.dzfl.pl - 专注于 D 的在线编译器和协作工具 中找到一个可运行的示例。
- 关联数组
struct Point { uint x; uint y; } // toHash is automatically generated by the compiler, if not user provided
Point[string] table; // hashtable string -> Data
table["Zero"] = Point(0, 0);
table["BottomRight"] = Point(uint.max, uint.max);
- 嵌套函数
- 闭包(C++11 添加了 lambda 函数,但通过引用捕获变量的 lambda 函数不允许逃逸创建它们的函数)。
- 内部类
D 中缺少 C++ 功能
[edit | edit source]- 预处理器
- 具有非虚拟析构函数的多态类型
- 多态值类型——在 D 中,
struct
是不支持继承和虚拟函数的值类型,class
是支持继承和虚拟函数的引用类型。 - 多重继承——D 类仅提供 Java 和 C# 风格的接口多重实现。相反,为了代码重用,D 更倾向于组合、
mixin
和alias this
。
有关更多详细信息,请参阅 D 编程 书籍。
章节摘要
[edit | edit source]
- ↑ "根据 TIOBE 指数,C++ 是第三大流行的编程语言". 2020年9月.