芝士wa
2024.3.29
Item2链接
“用compiler(编译器)取代 preprocessor(预处理器)”,问题就在于因为#define不被视为语言的一部分
要理解这句话,需要先了解C++程序的编译过程:
1.预处理 preprocess
- 预处理是编译前的准备工作。
- 在这一步中,预处理器会执行以下操作:
- 替换所有的宏定义(#define)
- 处理条件预编译指令(#if,#ifdef,#elif,#else,#endif)
- 处理#include指令,将头文件的内容插入到源文件中
- 删除注释
- 预处理之后,生成的文件通常以.i作为文件扩展名
2.编译 complie
- 编译器将预处理后的源代码转换为汇编代码
- 编译器检查词法和语法规则,但不进行逻辑检查
- 编译生成的文件通常以.s作为文件扩展名
3.汇编 assemble
- 汇编器将汇编代码转换为机器码(目标文件)
- 目标文件通常以.o作为文件扩展名
4.链接 link
- 链接器将多个目标文件合并成一个可执行的二进制文件
- 链接过程包括解决外部符号引用、合并目标文件和系统库文件等步骤
#define
#define是预处理命令,不被算作语言的一部分,例如
#define aspect_ratio 1.653
在预编译阶段,aspect_ratio被替换成实际的值1.653,编译器在得到源代码之前不知道aspect_ratio这个符号名,因此它不会被加入符号表中。
“ 如果在编译的时候,发现一个 constant(常量)使用的错误,你可能会陷入混乱之中,因为错误信息中很可能用 1.653 取代了 ASPECT_RATIO。如果,ASPECT_RATIO 不是你写的,而是在头文件中定义的,你可能会对 1.653 的出处毫无头绪,你还会为了跟踪它而浪费时间。”
解决方案是用constant取代宏定义
const double AspectRatio = 1.653
作为language constant(语言层面的常量),AspectRatio被编译器明确识别并加入符号表,另外,“对于 floating point constant(浮点常量)(比如本例)来说,使用 constant(常量)比使用 #define 能产生更小的代码。这是因为 preprocessor(预处理器)盲目地用 1.653 置换 macro name(宏名字)ASPECT_RATIO,导致你的 object code(目标代码)中存在多个 1.653 的拷贝,如果使用 constant(常量)AspectRatio,就绝不会产生多于一个的拷贝。”
宏定义的坏处:
- 作用域问题:宏定义是全局有效的,无法提供封装性。
- 类型安全问题:宏定义不进行类型检查,可能导致难以发现的错误。
- 符号表问题:宏定义不会在符号表中注册,因此无法在编译器中查找到
声明和定义的区别:
- 声明:告诉编译器某个名称的存在,但不分配内存或初始化它。例如,函数原型、类成员的声明等。
- 定义:为名称分配内存并初始化它。例如,变量的定义、函数的实现等。
对于static const类型的成员变量,有例外规则:
class GamePlayer {
private:
static const int NumTurns = 5; // constant declaration
int scores[NumTurns]; // use of constant
...
};
- 声明时初始化:可以在类的声明中初始化这些成员
- 不需要独立的定义:只要你不需要获取这些类常量的地址,你可以只声明并使用它们,而不提供独立的定义,编译器会在需要的地方直接使用初始化的值。
- 提供独立的定义:可以在类外提供,在实现文件中编写。
const int GamePlayer::Numturns;
枚举类型
如果编译器禁止静态类成员变量的初始化语法,可以用枚举类型enum绕过这个问题,使用enum定义一个匿名的枚举类型,将常量值作为枚举的成员,这样就可以实现在类的声明中初始化变量,而不需要独立的定义。
class GamePlayer {
private:
enum { NumTurns = 5 }; // "the enum hack" makes
// NumTurns a symbolic name for 5
int scores[NumTurns]; // fine
...
};
枚举类型的特点:
- 不能获取地址:你不能合法地获取枚举常量的地址,就像不能获取#define宏定义的地址一样。
- 不分配多余内存:枚举和#define都不会导致不必要的内存分配,因为它们不会在内存中存储常量的实际值。
内联函数
内联函数的目的是将函数体的代码插入并替代每一处调用该函数的地方,从而节省函数调用的开销。
- 减少函数调用开销: 内联函数避免了频繁调用函数对栈内存重复开辟所带来的消耗。相当于将函数体的代码直接放到调用处,而不是不断地开辟一个函数栈。
本章要点
-
对于 simple constants(简单常量),用 const objects(const 对象)或 enums(枚举)取代 #defines
-
对于 function-like macros(类似函数的宏),用 inline functions(内联函数)取代 #defines。