C++ 和 C 语言的联系

C++ 名称中的 ++ 其实就是 C 语言中的递增运算符(++),这个名称也表明 C++ 是 C 语言的扩充版本。

那么 C++ 是在 C 语言的基础上添加了什么
C++ 在 C 语言的基础上添加了面向对象编程和泛型编程的支持。面向对象编程的特性带来了全新的编程方法,这种方法可以应付复杂程度不断提高的现代编程任务。C++ 的模板特性则提供了另一种全新的编程方法 —— 泛型编程。这也使得 C++ 融合了 3 种不同的编程方式:C 语言传统的面向过程编程、C++ 新增的面向对象编程(Class)、C++ 模板支持的泛型编程
C++ 在 C 语言的基础上添加面向对象编程(OOP),这也使得 C++ 既能像 C 语言那样紧密联系硬件,也能使用 OOP 部分将涉及的概念联系起来。
image.png
前面我们提到了 C++ 在 C 语言的基础上添加了面向对象编程和泛型编程这两个编程方式,这其实是一个大方向上的变化。C++ 为了支持面向对象编程,新增了异常、运行阶段类型识别等知识;为了支持泛型编程,新增了模板和标准模板库(STL)等知识。
ISO C++ 标准还吸收了 ANSI C 标准,因为 C++ 应尽量是 C 语言的超集,这意味着在理想情况下,任何有效的 C 程序都应是有效的 C++ 程序。但是,需要知道的是,C++ 和 C 语言依旧有一些细节上的差异。

C++ 和 C 语言的差异

C++ 在 C 语言的基础上新增了面向对象编程泛型编程两大特性,这使得 C++ 新增了很多特性。
针对面向对象编程,C++ 提供了 class 关键字来创建类的声明、允许用户为自定义的类型进行运算符重载、支持函数重载、提供了异常处理机制。
针对泛型编程,C++ 提供了模板编程,并且还封装了标准模板库 STL。

基本数据类型

1. 内置基本数据类型

C 语言内置的整型数据类型有 char、short、int、long、long long,没有布尔类型,如果想要声明一个布尔类型的变量,需要 #inclued <stdbool.h>。导入 stdbool.h 这个头文件之后才可以用 _Bool 来声明一个布尔类型变量,以及使用 true 和 false 表示真假。
而 C++ 将 bool 类型作为内置的基本数据类型,用 true 和 false 表示真假。

2. 变量初始化

除了 C 语言支持的初始化方法,C++ 还有 C 语言没有的初始化语法。

  1. int olws = 1001; // C 语言初始化
  2. int nlws1(1001); // C++ 初始化
  3. int nlws2 = {1001};// C++
  4. int nlws3{1001}; // C++

数组等复合类型使用 { } 初始化时也可以省略等号(=),但不能使用 () 来初始化。

复合数据类型

1. 字符串

除了 C-风格字符串之外,C++ 可以使用 string 类来表示字符串。string 存储字符串比字符数组更灵活。

另外 C++11 新增了原始字符串。

  1. cout << R"(hello,"Tim", good)" << endl;
  2. // 输出:hello, "Tim", good

2. 结构

C++ 的结构中允许有函数,和 C++ 的 class 的差别在于 struct 的默认访问权限是 public,而 class 的默认访问权限是 private。
C 语言的结构中不允许有函数,但可以通过声明函数指针的方式来实现。

使用结构描述创建变量时,C++ 可以省略 struct 关键字,而 C 语言不允许省略 struct。

基本语法

1. 循环

C++ 新增了基于范围的 for 循环,这简化了一种常见的循环任务:对数组、容器类的每个元素执行相同的操作。例如,打印数组的元素:

  1. int arr[10] = {1, 3, 4, 2, 7, 1, 9, 10, 6, 7};
  2. for (int x : arr) {
  3. std::cout << x << std:: endl;
  4. }

如果需要改变元素的值,则需要使用引用:

  1. int arr[10] = {1, 3, 4, 2, 7, 1, 9, 10, 6, 7};
  2. for (int &x : arr) {
  3. x = x << 1; // 修改数组元素,如果声明的 x 不是引用,则此处的修改并不会影响到数组中的元素
  4. }
  5. for (int x : arr) {
  6. std::cout << x << " ";
  7. }
  8. std::cout << std:: endl;

2. 逻辑运算符

C++ 中可以使用 and、or、not 来代替逻辑运算符 &&、||、!,在 C++ 中 and/or/not 是保留字,不需要像 C 语言那样包含 头文件,C++ 不要求使用头文件。
C++ 中 and、or、not 是保留字,虽然不是关键字,但是也不能用作变量名。

函数

1. 函数原型

  1. 省略返回值。C 语言的函数头可以省略返回值,此时默认是 int 类型的返回值。而 C++ 逐步淘汰了这种用法。
  2. 参数列表中的 void。在 C 语言中,参数列表中为空意味着对是否接受参数保持沉默,是否接受参数需要看函数定义,而参数列表中使用 void 表示函数不接受任何参数。而 C++ 中,参数列表中为空和 void 是等效的。

    PS:对于这一点也可以看作是 C 语言不支持函数重载。

函数原型可以确保编译器能够正常处理函数返回值、检查使用的参数数目是否正确、使用的参数类型是否正确。
对于参数类型不匹配的情况,C++ 和 C 语言的处理方式不同。例如,如果函数参数是 int 类型(假设是 32 位),而程序员传递了一个 double 类型(假设 64 位)的参数。C 语言会检查 double 的前 32 位,并试图将其解释为一个 int 值。C++ 则是自动将传递的值转换为原型中指定的类型,条件是两者都是算术类型。
但是 C++ 的自动类型转换也不能避免所有可能的错误,仅当有意义时,原型才会类型转换。

2. 函数参数传值

C++ 在 C 语言的值传递和引用传递的基础上新增了引用传递。

引用和指针的区别
1. 引用必须在声明时进行初始化,且一旦绑定对象就不能再和其他对象绑定了,对于这一点可以将引用视为指针常量;
2. 引用一定不为空;
3. 引用效率比指针更高,因为指针还需要分配内存,而引用不需要。
4. 在函数声明使用引用形式的形参,使用函数时需要注意,如果传递的实参是非左值或者与引用参数类型不匹配的值时,实际上是传递了临时变量,这样在函数中修改这个变量不会影响实参的值。 —— 《C++ Primer Plus》262 页,将引用用作函数参数。

3. 重载

C++ 支持函数重载和运算符重载。

函数重载:函数名相同,参数列表不同的函数就是重载。

运算符重载:除成员访问运算符 (.)、成员指针访问运算符 (*)、域运算符 (::)、长度运算符 (sizeof)、条件运算符 (?:) 这五个运算符之外的其他运算符都可以被重载。前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符和 sizeof 运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特征。
例如,可以用 + 来拼接两个字符串。

4. 内联函数

C++ 新增内联函数替代宏函数,后来 C 语言也引入了内联函数。同样的,C++ 使用 const 关键字声明常量来代替宏定义,后来 C 语言也引入了 const 关键字。

5. 函数参数的默认值

C++ 支持函数参数使用默认值,使用默认值时需要注意不要和函数重载冲突。

6. 函数模板

这是 C++ 支持泛型编程而提供的新特性。

内存模型和命名空间

1. 动态分配内存

首先,C++ 中使用 malloc() 等动态分配内存的库函数时,类型转换更加严格
在 C 语言中允许将 void* 类型的数据赋值给其他类型的指针

  1. int* p = malloc(10 * sizeof(int)); // 允许
  2. free(p);

在 C++ 中将 void* 类型的数据赋值给其他类型的指针必须使用强制类型转换

  1. int* p = malloc(10 * sizeof(int)); // 不允许
  2. int* q = (int*) malloc(10 * sizeof(int)); // 允许
  3. free(q);

其次,C++ 可以使用 new 和 delete 运算符来申请和释放内存

2. auto 关键字

C 语言中 auto 关键字用来表示声明的变量是自动存储类型的变量,而 C++ 中 auto 关键字表示自动类型变量,可以根据初始化的值来判断变量的类型。
另外,C++11 使用 auto 进行后置返回类型声明来解决函数模板的一些问题。

  1. template <typename T1, typename T2>
  2. auto f(T1 & x, T2 & y) -> decltype(x + y);

3. register 关键字

在 C 语言中,register 关键字用来建议编译器使用 CPU 寄存器来存储自动变量,被 register 修饰的变量无法取地址。
在 C++11 之前,register 关键字的用法始终未变,但是在 C++11 中,关键字 regitster 只是显式地指出变量是自动的,这与 auto 以前的用途完全相同。

4. const 关键字

在 C++ 中,const 全局变量的链接性为内部的,也就是说,在 C++ 看来,全局 const 定义就像使用了 static 说明符一样。

  1. const int a = 10;
  2. // 上面的语句和下面的语句等价
  3. static const int a = 10;

5. 作用域解析运算符(::)

C++ 比 C 语言更进一步 —— 它提供了作用域解析运算符(::)。放在变量名前面时,该运算符表示使用变量的全局版本。

8. 头文件命名

C 语言的头文件都是以 h 作为后缀扩展名。例如,math.h、stdio.h、stdlib.h 等。这种方式可以通过文件名称识别文件类型为头文件。
C++ 的头文件没有扩展名,有些 C 语言的头文件被转换为 C++ 头文件,这些文件的扩展名会被去掉,并添加了 c 作为前缀。例如,C++ 版本的 math.h 为 cmath。对于纯粹的 C++ 头文件而言,去掉 h 不只是形式上的变化,没有 h 的头文件可以包含名称空间。

PS:名称空间也是 C++ 的一个新特性。

image.png
由于 C 语言使用不同的文件扩展名来表示不同文件类型,因此用一些特殊的扩展名来表示 C++ 头文件是有道理的,C++ 标准制定委员会也这样认为。但问题在于究竟使用哪种扩展名,因此最终他们一致决定同意不使用任何扩展名。