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开关。

相关推荐
wrx繁星点点12 分钟前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
真的想不出名儿16 分钟前
Java基础——反射
java·开发语言
努力编程的阿伟35 分钟前
【Java SE语法】抽象类(abstract class)和接口(interface)有什么异同?
java·开发语言
ctrey_36 分钟前
2024-11-1 学习人工智能的Day20 openCV(2)
人工智能·opencv·学习
Dola_Pan43 分钟前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
十年之少1 小时前
由中文乱码引来的一系列学习——Qt
学习
包饭厅咸鱼1 小时前
QML----复制指定下标的ListModel数据
开发语言·数据库
bryant_meng1 小时前
【python】Distribution
开发语言·python·分布函数·常用分布
红黑色的圣西罗1 小时前
Lua 怎么解决闭包内存泄漏问题
开发语言·lua
yanlou2331 小时前
KMP算法,next数组详解(c++)
开发语言·c++·kmp算法