C和C++的简要介绍、区别
C和C++的主要历史节点:
C语言于1969年至1973年间,为了移植与开发UNIX操作系统,由丹尼斯·里奇与肯·汤普逊,以B语言为基础,在贝尔实验室设计、开发出来。二十世纪八十年代,C语言应用日渐广泛。为了避免各开发厂商用的C语言的语法产生差异,美国国家标准局为C语言订定了一套完整的国际标准语法,称为ANSI C,作为C语言的标准。与此同时,国际标准化组织也接受该标准为国际标准。因此,ANSI C也同时被称为ISO C。二十世纪八十年代至今的有关程序开发工具,一般都支持符合ANSI C的语法。
其后,C语言至今经历了几次标准更新,诞生了C99、C11和目前最新的标准C18。C语言标准的下一次更新C2x目前正在起草中。
1983年,C with Classes改命名为C++(++是C语言中的增值操作符)。加入了新的特性,其中包括虚函数、函数名和运算符重载、参考、常量、用户可控制的自由空间存储区控制、改良的类型检查,并恢复了BCPL风格的双斜线(//)单行注释(之后C99也支持了这种注释)。
1985年,发布第一版《C++程序设计语言》,提供一个重点的语言参考,至此还不是官方标准[5]。1985年10月出现了第一个商业化发布。
1989年,发布了Release 2.0。引入了多重继承、抽象类别、静态成员函数、常量成员函数,以及成员保护。1990年,出版了The Annotated C++ Reference Manual。这本书后来成为标准化的基础。稍后还引入了模板、异常处理、命名空间、新的强制类型转换,以及布尔类型。
随着C++语言的演变,也逐渐演化出相应的标准程序库。最先加进C++标准函式库的是流I/O程序库,其用以取代传统的C函数,如printf和scanf。随后所引入的程序库中最重要的便是标准模板库,简称STL。
多年后,一个联合的ANSI-ISO委员会于1998年对C++标准化(ISO/IEC 14882:1998)。在官方发布1998标准的若干年后,委员会处理缺陷报告,并于2003年发布一个C++标准的修正版本。2005年,一份名为Library Technical Report 1(简称TR1)的技术报告发布。虽然还不是官方标准的一部分,不过它所提供的几个扩展可望成为下一版C++标准的一部分。几乎所有目前仍在维护的C++编译器皆已支持TR1。
目前最新的C++标准是2020年12月发布的ISO/IEC 14882:2020,又称C++20。
关于wiki中C和C++介绍的一些值得注意的点:
C:
与ALGOL一族的大多数过程式编程语言类似,C语言是一个有结构化程序设计、具有变量作用域(variable scope)以及递归功能的过程式语言。
其采用的静态类型系统可以防止无意的程序设计操作。
C语言中所有的可执行代码都被包含在子程序(函数)里。其传递参数均是以值传递(pass by value),另外也可以传递指针(a pointer passed by value)。
C语言是自由形式语言,即其源代码的缩进并不影响程序的功能,而是使用分号作为语句的结尾,花括号来表示代码块。
C语言的语言规模较小,若干高层的机制需要使用定义的函数来提供。比如,C语言并没有直接处理复合对象(例如字符串、集合、列表、数组等)的操作,也没有对于存储器分配工具和内存回收工具的直接定义,同时也本身不具有输入和输出以及文件访问的方法。用户定义的函数和C语言标准库中的函数为这些高层的机制提供了可能性。
C语言也具有以下的特性:
- 基本数据类型包括字符、整型和浮点数等。另外也有派生的各种数据类型,如指针、数组、结构和联合。
- 部分的变量类型可以转换,例如整数型和字符型变量。
- 透过指针(pointer),C语言可以容易的对存储器进行低端控制。
- 不同的变量类型可以用结构体(struct)组合在一起。
- 具有基本的控制流:语句组、条件判断、多路选择、循环等。
- 函数可以返回各种数据类型的值,并且都可以递归调用。每次调用函数会重新创建变量。
- C语言只有32个保留字(reserved keywords),使变量、函数命名有更多弹性。
- 编译预处理(preprocessor)让C语言的编译更具有弹性。
C++:
比雅尼·斯特劳斯特鲁普博士在贝尔实验室工作期间在20世纪80年代发明并实现了C++。起初,这种语言被称作“C with Classes”(“包含‘类’的C语言”),作为C语言的增强版出现。随后,C++不断增加新特性。虚函数、运算符重载、多继承、标准模板库、异常处理、运行时类型信息、命名空间等概念逐渐纳入标准草案。
作为广泛被使用的工业语言,C++存在多个流行的成熟实现:GCC、基于LLVM的Clang以及Visual C++等。这些实现同时也是成熟的C语言实现,但对C语言的支持程度不一(例如,VC++对ANSI C89之后的标准支持较不完善)。
值得注意,和流行的误解不同,ISO C和ISO C++都从未明确要求源程序被编译(compile),而仅要求翻译(translate),因此C和C++并不是所谓的编译型语言。技术上,实现C和C++程序的单位是翻译单元(translation unit)。作为对比,Java语言规范中就明确要求Java程序被编译实现,明确存在编译单元(compilation unit)。实际上C和C++也存在REPL形式的解释器实现,如CINT和Cling。但因为传统上C和C++多以编译器实现,习惯上仍有一些混用,甚至至今仍出现在ISO C++某节标准库条款的标题 (页面存档备份,存于互联网档案馆)上。
C++的特色
和C语言相比,C++引入了更多的特性,包括:复合类型(引用类型等)、const限定符和constexpr常量表达式、类型处理运算符(类型别名及auto和decltype等多种类型指示符)、C++标准库(IO库与多种容器类)与迭代器、动态内存与智能指针、函数重载、面向对象程序设计(如数据抽象、成员函数、类作用域、构造函数与析构函数、静态成员、访问控制与继承、虚函数、抽象类与接口等)、拷贝控制、运算符重载、造型与函数风格的强制类型转换、模板与泛型编程,以及异常处理、命名空间、多继承与虚继承、运行时类型识别及嵌套类等。
C++在某些案例中(见下“与C不兼容之处”),进行比C还要多的类型检查。
以“//”起始作为注解起源自C的前身BCPL,而后被重新引入到C++。
C++的一些特性,C不久之后也采用了,包括在for循环的括号中声明,C++风格的注解(使用//符号,和inline,虽然C99定义的inline关键字与C++的定义不兼容。不过,C99也引入了不存在于C++的特性,如:可变参数宏,和以数组作为参数的较佳处理;某些C++编译器可能实现若干特性,以作为扩展,但其余部分并不符合现存的C++特性)。(所以C和C++是相互发展的)
一个常见的混淆其实只是一个微妙的术语问题:由于它的演化来自C,在C++中的术语对象和C语言一样是意味着存储器区域,而不是类的实体,在其它绝大多数的面向对象语言也是如此。举例来说,在C和C++中,语句int i;定义一个int类型的对象,这就是变量的值i将在指派时,所存入的存储器区域。
C++不是第一个正式引入const类型的语言。80年代早期,Bjarne Stroustrup和Dennis Retchie讨论之后提供了在C语言中readonly/writeonly的实现机制,并在带类的C中获取了一定经验。关键字const正式引入C语言是在ANSI C89。这早于第一个C++国际标准近十年,但此时const已被C++实现普遍采用。
以下是和C语言兼容的用法:
1
2
3
4
5
6
7
8
int m = 1, n = 2; // int 类型的对象
const int a = 3; // const int 类型的对象
int const b = 4; //同上
const int *p //指向 const int 类型对象的指针
int const *q; //同上
int *const x; //指向 int 类型对象的 const 指针;注意 const 的位置
const int *const r; //指向 const int 类型对象的 const 指针
int const *const t; //同上
但是,const在C++中有更强大的特性。它允许在编译时确定作为真正的常量表达式。例如,
1
2
const int max_len = 42;
int a[max_len];
此前C语言并不支持这样的用法,直到C99允许用变量作为数组长度(需要注意的是C99中的VLA支持运行期确定数组长度,但C++从未支持)。此外,C++中,命名空间作用域的const对象的名称隐含内部链接。这意味着直接在头文件里定义const对象被多个源文件包含时,也不会重定义。(不同版本的C标准代码不一样适用)
C++和C不兼容的地方
C++有时被认为是C的超集(superset),但这并不严谨。 (两者的标准是相互发展的)
最常见的差异之一是,C允许从void*
隐式转换到其它的指标类型,但C++不允许。下列是有效的C代码:
1
2
// 从void *隐式转换为int *
int *i = malloc(sizeof(int) * 5);
但要使其在C和C++两者皆能运作,就需要使用显式转换:
1
int *i = (int *)malloc(sizeof(int) * 5);
另一个常见的可移植问题是,C++定义了很多的新关键字,如new
和class
,它们在C程序中,是可以作为识别字(例:变量名)的。
C99去除了一些不兼容之处,也新增了一些C++的特性,如//
注释,以及在代码中混合使用。不过C99也纳入几个和C++冲突的新特性(如:可变长数组、原生复数类型和复合逐字常数),而C++11已经加入了兼容C99预处理器的特性。
由于C++函数和C函数通常具有不同的名字修饰和调用约定,所有在C++中调用的C函数,须放在extern "C" { /* C函数声明 */ }
之内。
C++的hello world程序
下面这个程序显示“Hello, world!”然后结束运行:
1
2
3
4
5
6
7
8
9
#include <iostream>
// import <iostream>; // C++20 起
// import std; // C++23 起
int main() {
std::cout << "Hello, world!" << std::endl;
// std::println("Hello, world!"); // C++23 起
return 0;
}
这里也可以使用using指令以避免多次声明std::——
1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
int main() {
cout << "Hello, world!" << endl;
return 0;
}
根据ISO C++的规定,全局main函数必须返回int。 以下两种形式是合法的:
1
2
3
4
5
6
7
int main() {
// ...
}
int main(int argc, char *argv[]) {
// ...
}
不过,在一些编译器(例如Visual C++)上,
1
2
3
void main() {
// ...
}
也是合法的。但是这样的写法兼容性较差。
C和C++的特性
C
C++
C++主要有三个编译阶段:预处理、转译成目标代码和链接(最后的两个阶段一般才视为真正的“编译”)。在第一阶段,预处理,会将预处理器指令替换成源代码,然后送到下一个编译阶段。
预处理指令的运作方式是根据用户定义的规则,简单的把记号字符序列置换成其它的记号字符序列。它们进行宏置换、含入其它的文件(由底层至高阶的特性,例如包含模块/包/单元/组件)、条件式编译和条件式含入。例如:
1
#define PI 3.1415926535897932384626433832795028841971693993751
原始代码中出现的PI,都将会替换为3.1415926535897932384626433832795028841971693993751。另一个普遍的例子是
1
#include <iostream>
它将使用标准库头文件iostream
中的所有内容来替换本条预处理指令。除了以上提到的常用指令以外,还有几个额外的预处理器指令,可以用来控制编译流程、条件式含入或排除代码区块等等。
模板(Template)指C++编程语言中的函数模板(function template)与类模板(class template),这种观念是取材自Simula的泛型程序设计。它采用typename和class两个关键字,来标识模板类别的类型参数。C++11和C++14分别引入了类型别名模板和变量模板。
封装(Encapsulation)是将资料和处理资料的程序(procedure)组合起来,仅对外公开接口(interface),达到信息隐藏(information hiding)的功能。封装的优点是能减少耦合(Coupling)。C++、Java、C# 等语言定义对象都是在语法中明确地使用类别(Class)来做到封装。
C++的类对其成员(包括数据成员、函数成员)分为三种封装状态:
- 公有(public):类别的用户可以访问、使用该类别的此种成员。
- 保护(protected):该类别的派生类可以访问、使用该类别的此成员。外部程序代码不可以访问、使用这种成员。
- 私有(private):只有类别自身的成员函数可以访问、使用该类别的此成员。
一般可以将C++类的对外接口设定为公有成员;类内部使用的数据、函数设定为私有成员;供派生自该类别的子类使用的数据、函数设定为保护成员
继承(Inheritance)是指派生类(subclass)继承基类(superclass),会自动获取超类别除私有特质外的全部特质,同一类别的所有实体都会自动有该类别的全部特质,做到代码再用(reuse)。C++只支持类别构成式继承,虽然同一类别的所有实体都有该类别的全部特质,但是实体能够共享的实体成员只限成员函数,类别的任何实体资料成员乃每个实体独立一份,因此对象间并不能共享状态,除非特质为参考类型的属性,或使用指针来间接共享。C++支持的继承关系为:
- 公有继承(public inheritance):最常用继承关系,含义是“is-a”关系,代表了在完全使用公有继承的对象类别之间的层次关系(hierarchy)。
- 受保护继承(protected inheritance):基类的公有或保护内容可以被派生类,以及由此派生的其他类别使用。但是基类对外界用户是不可见的。派生类的用户不能访问基类的成员、不能把派生类别转换(造型)为基类的指针或引用。
- 私有继承(private inheritance):基类的公有或保护内容仅可以被派生类访问。但基类对派生类的子类或派生类的用户都是不可见的。派生类的子类或派生类的用户都不能访问基类的内容、不能把派生类转换为基类的指针或引用。
C++支持多继承(multiple inheritance,MI)。多继承(multiple inheritance,MI)的优缺点一直广为用户所争议,许多语言(如Java)并不支持多重继承,而改以单一继承和接口继承(interface inheritance),而另一些语言则采用用单一继承和混入(mixin)。C++通过虚继承(Virtual Inheritance)来解决多继承带来的一系列问题。
除了封装与继承外,C++还提供了多态功能,面向对象的精神在于多态(Polymorphism),一般的多态,是指动态多态,系使用继承和动态绑定(Dynamic Binding)实现,使用多态可建立起继承体系(Inheritance hierarchy)。类(class)与继承只是达成多态中的一种手段,所以称面向对象而非面向类。
多态又分成静态多态(Static Polymorphism)与动态多态(Dynamic Polymorphism)。C++语言支持的动态多态必须结合继承和动态绑定(Dynamic Binding)方式实现。静态多态是指编译时决定的多态,包括重载和以模板(template)实现多态的方法即参数化类型(Parameterized Types),是使用宏(macro)的“程序代码膨胀法”达到多态效果。
类型转换(type cast)也是一种非参数化(ad hoc)多态的概念,C++提供dynamic_cast, static_cast等运算符来实现强制类型转换(Coercion)。
操作数重载(operator overloading)或函数重载(function overloading)也算是多态的概念。