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

相关推荐
EmbedLinX6 分钟前
嵌入式之协议解析
linux·网络·c++·笔记·学习
楚轩努力变强7 分钟前
iOS 自动化环境配置指南 (Appium + WebDriverAgent)
javascript·学习·macos·ios·appium·自动化
凉、介9 分钟前
VMware 三种网络模式(桥接 / NAT / Host-Only)原理与实验解析
c语言·网络·笔记·操作系统·嵌入式·vmware
盐焗西兰花11 分钟前
鸿蒙学习实战之路-Reader Kit自定义页面背景最佳实践
学习·华为·harmonyos
薛定谔的猫喵喵11 分钟前
基于PyQt5的视频答题竞赛系统设计与实现
开发语言·qt·音视频
岱宗夫up17 分钟前
Python 数据分析入门
开发语言·python·数据分析
xhbaitxl17 分钟前
算法学习day38-动态规划
学习·算法·动态规划
Aliex_git17 分钟前
跨域请求笔记
前端·网络·笔记·学习
tritone18 分钟前
使用阿贝云免费云服务器学习Vagrant,是一次非常顺畅的体验。作为一名开发者
服务器·学习·vagrant
wangjialelele18 分钟前
Linux中的进程管理
java·linux·服务器·c语言·c++·个人开发