Skip to content

From C to C++

C++ 类与对象

C++ 类 (Class) 可以看做是 C 中结构体 (Struct) 升级版。Class 的成员不仅仅可以是变量还可以是函数,类的成员变量称为“属性 (Property)”,成员函数称为“方法(Method)”。通过类定义出来的变量叫做“对象(Object)”,创建对象的过程叫做类的实例化,因此也称对象是类的一个“实例 (Instance)”。

C++ 的编译与运行

C 语言源文件后缀都是 .c,头文件后缀都是 .h;但对于 C++,不同编译器支持的后缀不太一样,常用的 C++ 头文件后缀有 .h.hpp.hxx,常用的 C++ 源文件的后缀有 .cpp.cc.cxx.C.c++

在 C 语言中,我们可以使用 gcc 命令来编译和链接 C 程序。编译 C++ 时,gcc 命令也可以使用,不过要增加 -lstdc++ 选项;不过 GCC 中还有一个专门用来编译 C++ 程序的 g++ 命令,也可以用它来编译 C++。

C++ 命名空间

为了解决合作开发时的命名冲突问题,C++ 引入了命名空间 (Namespace),定义一个命名空间的语法格式为:

namespace name {
    // #define
    // typedef
    // variables
    // functions
    // classes
    // ...
}

:: 为 C++ 中的新符号,称为”域解析操作符 (scope resolution operator)“,要使用命名空间中定义的变量函数等,可以用 name::variablename::function 等来使用。

除了直接使用域解析操作符,还可以使用 using 关键字声明:

using name::variable;
// 就可以直接使用 variable
variable = 1;

using 关键字也可以直接声明整个命名空间:

using name; 
// 后续作用范围内的代码可以直接使用 name 中定义的 variables,functions,...
// 若未具体指定命名空间的变量发生命名冲突,默认采用命名空间 name 中的变量

std 是 C++ 标准库类型对象的命名空间。

早期 C++ 不完善的时候,还不支持命名空间,这时 C++ 还在使用 C 的库,stdio.hstdlib.hstring.h 等头文件仍然有效,C++ 也开发了一些新的库,添加了如 iostream.hfstream.hcomplex.h 的头文件。

后来 C++ 引入命名空间的概念,将库、类、函数、宏等都统一纳入一个命名空间,即 std,但为了兼容早期的老式 C++,故保留原来的 .h 头文件和库,然后把原来的库复制一份,并稍加修改后将库、类、函数、宏等纳入命名空间 std。为了避免头文件重名,新版的 C++ 库的头文件,对于 C++ 语言新增的头文件,其命名去掉了 .h 后缀,即 iostream.h 变为了 iostream;而原来的 C 语言头文件,去掉后缀 .h 并添加前缀 c,即 stdio.h 变为了 cstdio

虽然早期的 C++ 头文件(如 iostream.h)与 C 头文件(如 stdio.h)仍被支持,但其内容却不在命名空间 std 中。因此,对原来的头文件,即使按 C++ 方式来引入(如 #include <cstdio>),其符号也既可以位于命名空间 std 中,可以位于全局范围内。

C++ 输入输出

C 语言中,通常使用 scanfprintf 进行输入输出,而在 C++ 中,这两个函数仍可以使用,但 C++ 添加一套新的,更容易使用的输入输出库。即使用 <iostream> 中定义的输入输出对象 cincoutcerr。如下是一个示例,具体使用于后续章节。

int num;
std::cin >> num;
std::cout << "Hello World! Input is:" << num << std::endl;

C++ 变量定义位置

C 语言中,C89 规定,所有局部变量必须定义在函数开头,C99 标准取消了这条限制。C++ 标准取消了这个限制,变量只要在使用之前定义好即可。

C++ 布尔类型

在 C 语言中表示真假: 0 为假,非 0 为真。而 C++ 中新增 bool 类型,一般占 1 字节长度,值为 true(真) 和 false(假)。

C++ 常量 (Constant)

在 C 语言中,const 用来限制一个变量,表示这个变量不能修改。在 C++ 中,const 的含义并没有改变,但是其有一些细节上的不同。

  1. C 语言中,const 变量的读取需要访问内存;而在 C++ 中,对 const 变量的取值会在编译阶段完成值替换,有点类似于 #define,但是 #define 的值替换是在预处理阶段。
  2. C 语言中,const 全局变量的可见范围是整个程序,在其他文件中使用 extern 声明即可使用;但在 C++ 中 const 全局变量的可见范围仅限于当前文件,在其他文件中不可见,因此可以定义在头文件中,多次引入也不会出错。

C++ 动态内存分配管理

C 语言中,动态内存管理用 malloc()free() 函数。C++ 中,这两个函数仍可以使用,但新增两个关键字,newdelete

int *p = new int;
int *arr = new int[24];
delete p;
delete[] arr;

C++ 中,建议使用 newdelete 来管理内存分配,其最明显的特性是可以自动调用构造函数和析构函数。

C++ inline 函数

为了消除函数调用的时空开销,C++ 提供了一种高效的方法 —— 内联函数(Inline Function),其在编译时会将函数调用用函数体替换掉。(inline 声明只是程序员对编译器提出的建议,怎样做由编译器自己决定。)

inline int func(int a, int b) {
    // functions body
}

由于内联函数会在编译期间进行替换,所以我们可以将容易踩坑的宏 (#define) 替换为内联函数。如:

// 宏 SQ 可以替换为内联函数 sq
#define SQ(y) ((y) * (y))
inline int sq(int y) { return y * y; }

inline 关键字要在函数定义处添加,在函数声明处的 inline 关键字会被编译器忽略。

更严格来说,内联函数不应该有声明。在多文件编程中,通常会把函数定义放在源文件,函数声明放在头文件,但这种做法不适用于内联函数,将内联函数的声明和定义分放在不同文件会出错。这是因为程序会先在编译期间用对内联函数进行替换,替换后,内联函数定义将不存在,其后进行链接时,有内联函数声明的文件将找不到内联函数定义,从而链接错误。

C++ 函数重载

在 C 语言中,要实现功能相近但参数列表不同的函数,只能分别设计出多个不同函数名的函数。但在 C++ 中,可以实现函数重载 (Function Overloading),它允许多个函数拥有相同的名字,只要他们的参数列表不同就可以(参数列表不同包含参数类型、数量或顺序的不同)。注意仅仅是返回值的不同不能作为重载的依据。

这是因为 C++ 编译时会根据参数列表对函数进行重命名,如 bool func(int a, int b) 会被重命名为 _func_int_int,而 bool func(float a, float b) 会被重命名为 _func_float_float(不同编译器有不同的重命名方式,仅做举例说明)。

函数重载匹配优先级:

  1. 精确匹配
  2. 直接匹配
  3. 从数组名到数组指针,从函数名到函数指针,从非 constconst 的类型转换
  4. 类型提升后匹配(不会降低数据精度)
  5. 整型:boolcharshort 转换为 intchar16_tchar32_twchar_t 转换为 intlonglong long
  6. 浮点:float 转换为 double
  7. 自动类型转换后匹配(除类型提升外的可行的类型转换,精度可能会降低)

如果在同一个优先级中,找到多个合适的重载函数,编译器不知道如何抉择,就会发生编译错误。若函数有多个参数,而有参数对不同函数重载的优先级胜出结果不一致,编译器也会不知如何抉择,从而发生二义性错误。

C++ 与 C 混合编程

C++ 支持函数重载,会在编译阶段对函数名进行重命名;而 C 不支持函数重载,所以并不会在编译阶段对函数名进行太大的修改。由于 C 和 C++ 对函数名的处理不同,若要进行 C 和 C++ 的混合编程,比如将 test1.c 和 test2.cpp 编译链接,将会在链接阶段无法找到函数的具体实现而报错。

C++ 中提供了相应的解决方案,即借助 extern "C"extern "C" 可以用来修饰一句/段 C++ 代码,其作用是让编译器以 C 的方式来处理修饰 C++ 代码。对于 C 和 C++ 混合编程的问题,可以在头文件中使用如下格式:

#ifdef _cplusplus
extern "C" {
#endif

/* 需要修饰的声明 */
void func(int a);

#ifdef _cplusplus
}
#endif