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),定义一个命名空间的语法格式为:
::
为 C++ 中的新符号,称为”域解析操作符 (scope resolution operator)“,要使用命名空间中定义的变量函数等,可以用 name::variable
, name::function
等来使用。
除了直接使用域解析操作符,还可以使用 using
关键字声明:
using
关键字也可以直接声明整个命名空间:
using name;
// 后续作用范围内的代码可以直接使用 name 中定义的 variables,functions,...
// 若未具体指定命名空间的变量发生命名冲突,默认采用命名空间 name 中的变量
std
是 C++ 标准库类型对象的命名空间。
早期 C++ 不完善的时候,还不支持命名空间,这时 C++ 还在使用 C 的库,
stdio.h
、stdlib.h
、string.h
等头文件仍然有效,C++ 也开发了一些新的库,添加了如iostream.h
、fstream.h
、complex.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 语言中,通常使用 scanf
和 printf
进行输入输出,而在 C++ 中,这两个函数仍可以使用,但 C++ 添加一套新的,更容易使用的输入输出库。即使用 <iostream>
中定义的输入输出对象 cin
、 cout
、cerr
。如下是一个示例,具体使用于后续章节。
C++ 变量定义位置
C 语言中,C89 规定,所有局部变量必须定义在函数开头,C99 标准取消了这条限制。C++ 标准取消了这个限制,变量只要在使用之前定义好即可。
C++ 布尔类型
在 C 语言中表示真假: 0 为假,非 0 为真。而 C++ 中新增 bool
类型,一般占 1 字节长度,值为 true
(真) 和 false
(假)。
C++ 常量 (Constant)
在 C 语言中,const
用来限制一个变量,表示这个变量不能修改。在 C++ 中,const
的含义并没有改变,但是其有一些细节上的不同。
- C 语言中,
const
变量的读取需要访问内存;而在 C++ 中,对const
变量的取值会在编译阶段完成值替换,有点类似于#define
,但是#define
的值替换是在预处理阶段。 - C 语言中,
const
全局变量的可见范围是整个程序,在其他文件中使用extern
声明即可使用;但在 C++ 中const
全局变量的可见范围仅限于当前文件,在其他文件中不可见,因此可以定义在头文件中,多次引入也不会出错。
C++ 动态内存分配管理
C 语言中,动态内存管理用 malloc()
和 free()
函数。C++ 中,这两个函数仍可以使用,但新增两个关键字,new
和 delete
。
C++ 中,建议使用 new
和 delete
来管理内存分配,其最明显的特性是可以自动调用构造函数和析构函数。
C++ inline 函数
为了消除函数调用的时空开销,C++ 提供了一种高效的方法 —— 内联函数(Inline Function),其在编译时会将函数调用用函数体替换掉。(inline
声明只是程序员对编译器提出的建议,怎样做由编译器自己决定。)
由于内联函数会在编译期间进行替换,所以我们可以将容易踩坑的宏 (#define
) 替换为内联函数。如:
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
(不同编译器有不同的重命名方式,仅做举例说明)。
函数重载匹配优先级:
- 精确匹配
- 直接匹配
- 从数组名到数组指针,从函数名到函数指针,从非
const
到const
的类型转换 - 类型提升后匹配(不会降低数据精度)
- 整型:
bool
、char
、short
转换为int
,char16_t
、char32_t
、wchar_t
转换为int
、long
、long long
。 - 浮点:
float
转换为double
- 自动类型转换后匹配(除类型提升外的可行的类型转换,精度可能会降低)
如果在同一个优先级中,找到多个合适的重载函数,编译器不知道如何抉择,就会发生编译错误。若函数有多个参数,而有参数对不同函数重载的优先级胜出结果不一致,编译器也会不知如何抉择,从而发生二义性错误。
C++ 与 C 混合编程
C++ 支持函数重载,会在编译阶段对函数名进行重命名;而 C 不支持函数重载,所以并不会在编译阶段对函数名进行太大的修改。由于 C 和 C++ 对函数名的处理不同,若要进行 C 和 C++ 的混合编程,比如将 test1.c 和 test2.cpp 编译链接,将会在链接阶段无法找到函数的具体实现而报错。
C++ 中提供了相应的解决方案,即借助 extern "C"
。extern "C"
可以用来修饰一句/段 C++ 代码,其作用是让编译器以 C 的方式来处理修饰 C++ 代码。对于 C 和 C++ 混合编程的问题,可以在头文件中使用如下格式: