C++学习笔记----10、模块、头文件及各种主题(二)---- 预处理指令

使用#include预处理指令来包含头文件的内容。还有一些预处理指令。下面列表展示了一些常用的预处理指令:

|------------------------------------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
| 预处理指令 | 功能 | 通用场景 |
| #include [file] | [file]文件名的内容插入到指令位置的代码中 | 几乎总是用于包含头文件以便代码可以使用定义在其它地方的功能 |
| #define [id] [value] | 每个标识符[id]出现的地方都用[value]替换 | 常用于C定义常数或宏。c++对于常数与大部分类型的宏提供了更好的方法。宏可能会危险,所以要小心使用。 |
| #undef [id] | 使前面使用#define定义的[id]失效 | 如果定义的标识符只要求在限定范围的代码中使用。 |
| #if [expression] #elif [expression] #else #endif | 基于给定表达式的结果有条件包含代码块 | 常用于提供特定平台的特定代码 |
| #ifdef [id] #endif #ifndef [id] #endif | 基于特定标识符是否使用#define定义有条件地包含代码。#ifdef [id]与#if defined(id)等价,#ifndef [id]与#if !defined(id)等价 | 最常用于保护循环包含。以#ifndef开头的头文件检查是否有标识符的定义,接着用#define指令定义该标识符。头文件以#endif结束。这防止文件被包含多次。 |
| #elifdef [id] #elifndef [id] | #elifdef [id]与#elif defined(id)等价,#elifndef [id]与#elif !defined(id)等价 | 其它功能的缩写。 |
| #pragma [xyz] | 控制特定编译器的行为。[xyz]是编译器依赖的。大部分编译器支持once来防止头文件包含多次 | |
| #error [message] | 使编译以给定的信息停止。 | 如果用户想在不支持的平台上编译代码,可用于停止编译。 |
| #warning [message] | 使编译器发出给定警告信息,但是编译继续。 | 在不影响编译结果的情况下用于显示警告给用户。 |

1、预处理宏

可以使用C++预处理指令书写宏,看起来像小函数。举例如下:

cpp 复制代码
#define SQUARE(x) ((x) * (x)) // No semicolon after the macro definition!

int main()
{
    println("{}", SQUARE(5));
}

宏是C的产物,与inline函数很像,但是不进行类型检查,预处理只是单纯地将对其的调用进行替换。预处理器不执行真实的函数调用语法检查。这种行为可能产生不可预知的结果。例如,考虑如果用2+3而不是5来调用SQUARE宏会发生什么,像这样:

cpp 复制代码
println("{}", SQUARE(2 + 3));

你想让SQUARE计算出25,它确实这么做了。然而,如果你漏掉了宏定义中的一些括号,看起来像这样?

cpp 复制代码
#define SQUARE(x) (x * x)

现在对SQUARE(2+3)的调用生成了11,而不是25!记住宏只是单纯地替换而不进行函数调用语法的检查。这就意味着宏体中的x被替换为了2+3,变成如下的语句:

cpp 复制代码
println("{}", (2 + 3 * 2 + 3));

遵守运算的优先级顺序,这行代码行执行乘法,接着是加法,生成11而不是25!

宏也有性能影响。假定调用SQUARE宏如下:

cpp 复制代码
println("{}", SQUARE(veryExpensiveFunctionCallToComputeNumber()));

预处理器替换如下:

cpp 复制代码
println("{}", ((veryExpensiveFunctionCallToComputeNumber()) *
(veryExpensiveFunctionCallToComputeNumber())));

现在调用了昂贵的函数两次--另一个避免使用宏的原因。

宏在排错时也会出问题,因为你写的代码不是编译器看到的代码,或者说是在debugger中显示的代码(因为预处理器的查找替换行为)。由于这些原因,应该避免使用宏,要使用内联函数。讲这么多就是因为确实有一些代码还在使用宏。需要理解、阅读并维护这样的代码。

注意:大部分编译器可以输出预处理源码到文件或标准输出。可以使用这个我来观察预处理器怎么预处理你的代码。例如,在微软Visual C++中可以使用/P开关。GCC中使用-E开关。

相关推荐
脑斧猴9 分钟前
Linux中进程
linux·服务器·c++
tan180°13 分钟前
Linux自行实现的一个Shell(15)
linux·服务器·c++·后端·vim
江沉晚呤时20 分钟前
深入解析策略模式在C#中的应用与实现
java·服务器·开发语言·前端·.netcore
居然是阿宋21 分钟前
Kotlin 中的 `reified` 关键字全解析:保留类型信息 + 优化高阶函数的双重魔法
android·开发语言·kotlin
YiYueHuan42 分钟前
深入理解 GLOG_minloglevel 与 GLOG_v:原理与使用示例
c++·glog
HANG_WORLD1 小时前
接口(interface) 测试
学习·selenium·测试工具
安迪小宝1 小时前
python基础语法13-装饰器
开发语言·前端·python
小八四爱吃甜食1 小时前
【R语言绘图】圈图绘制代码
开发语言·r语言
残月只会敲键盘1 小时前
C++ Lambda表达式简明指南:新手快速上手
开发语言·c++
放羊大亨1 小时前
《认知觉醒》下篇·第六章第一节“清晰:一个观念,重构你的行动力” 总结
笔记