C/C++预处理器在源代码编译之前对其进行一些文本性质的操作。 它的主要任务包括删除注释 、 插入 #include 指令包含的文件的内容 、 定义和替换由 #defme 指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译 。

从概念上讲,预处理器是编译过程中单独执行的第一个步骤。两个最常用的预处理器指令是:

  • #include指令( 用于在编译期间把指定文件的内容包含进当前文件中);
  • #define指令( 用任意字符序列替代一个标记);

1. 预定义符号


示例代码

int main()
{cout << "__FILE__:" << __FILE__ <<  endl;   // __FILE__:main.cppcout << "__LINE__:" << __LINE__ <<  endl;   // __LINE__:11cout << "__DATE__:" << __DATE__ <<  endl;   // __DATE__:Jul  1 2021cout << "__TIME__:" << __TIME__ <<  endl;   // __TIME__:11:57:34cout << "__STDC__:" << __STDC__ <<  endl;   // __STDC__:1return 0;
}

2. 宏替换

宏定义的形式如下:

#define 名字  要替换的文本

这是一种最简单的宏替换——后续所有出现名字记号的地方都将被替换为替换文本。 #define指令中的名字与变量名的命名方式相同,替换文本可以是任意字符串。通常情况下,#define指令占一行,替换文本是 #define指令行尾部的所有剩余部分内容,但也可以把一个较长的宏定义分成若干行,这时需要在待续的行末尾加上一个反斜杠符 \

#define指令定义的名字的作用域从其定义点开始,到被编译的源文件的末尾处结束。宏定义中也可以使用前面出现的宏定义。替换只对记号进行,对括在引号中的字符串不起作用。例如,如果 YES是一个通过 #define指令定义过的名字,则在 printf( "YES")YESMAN中将不执行替换。

替换文本可以是任意的,例如:

#define forever for (;;) / * 无限循环 */

该语句为无限循环定义了一个新名字 forever

宏定义也可以带参数,这样可以对不同的宏调用使用不同的替换文本。例如,下列宏定义定义了一个宏 max:

#define max(A, B) ((A) > (B) ?  (A) : (B) )

使用宏 max 看起来很像是函数调用,但宏调用直接将替换文本插入到代码中。形式参数( 在此为A或B ) 的每次出现都将被替换成对应的实际参数。因此,语句:

x = max(p+q, r+s) ;

将被替换为下列形式:

x = ((p+q) > (r+s) ?  (p+q) : (r+s) )

仔细考虑一下 max的展开式,就会发现它存在一些缺陷。其中,作为参数的表达式要重复计算两次,如果表达式存在副作用( 比如含有自增运算符或输入/ 输出),则会出现不正确的情况。例如:

max(i++, j++)

它将对每个参数执行两次自增操作。同时还必须注意,要适当使用圆括号以保证计算次序的正确性。考虑下列宏定义:

#define square(x) x*x

当用 square(a+1)调用该宏定义时就会出现问题。

可以通过 #undef指令取消名字的宏定义,这样做可以保证后续的调用是函数调用,而不是宏调用:

#undef getchar
int getchar(void)
{...
}

3. 条件编译

使用条件编译,你可以选择代码的一部分是被正常编译还是完全忽略,用于支持条件编译的基本结构是 #if 指令和与其匹配的 #endif 指令 。

#if语句对其中的常量整型表达式(其中不能包含 sizeof、类型转换运算符或 enum常量)进行求值,若该表达式的值不等于 0,则包含其后的各行,直到遇到 #endif#elif#else语句为止(预处理器语句 #elif类似于 else if)。

#if语句中可以使用表达式 defined(名字),该表达式的值遵循下列规则:当名字已经定义时,其值为 1 ;否则,其值为 0 。

例如,为了保证 hdrh文件的内容只被包含一次,可以将该文件的内容包含在下列形式的条件语句中:

#if !defined ( HDR )
#define HDR
/* hdr.h 文件的内容放在这里 */
#endif

第一次包含头文件 hdrh时,将定义名字 HDR;此后再次包含该头文件时,会发现该名字已经定义,这样将直接跳转到 #endif处。类似的方式也可以用来避免多次重复包含同一文件。

如果多个头文件能够一致地使用这种方式,那么,每个头文件都可以将它所依赖的任何头文件包含进来,用户不必考虑和处理头文件之间的各种依赖关系。

下面的这段预处理代码首先测试系统变量 SYSTEM,然后根据该变量的值确定包含哪个版本的头文件:

#if SYSTEM == SYSV#define HDR "sysv.h"
#elif SYSTEM == BSD#define HDR "bsd.h"
#elif SYSTEM == MSDOS#define HDR "msdos.h"
#else#define HDR "default.h"
#endif# include HDR

C/C++ 语言专门定义了两个预处理语句 #ifdef#ifndef 定义。上面有关 #if的第一个例子可以改写为下列形式:

#ifndef HDR
#define HDR
/* hdr.h 文件的内容放在这里 */
#endif

4. 文件包含

4.1 库文件和本地文件包含

文件包含指令( 即 #include指令)使得处理大量的 #define指令以及声明更加方便。在源文件中,任何形如:

#include “文件名”

或者

#include <文件名>

的行都将被替换为由文件名指定的文件的内容。

  • 如果文件名用引号引起来,则在源文件所在位置査找该文件;
  • 如果在该位置没有找到文件,或者如果文件名是用尖括号 <>括起来的,则将根据相应的规则查找该文件,这个规则同具体的实现有关;

被包含的文件本身也可包含 #include指令。

源文件的开始处通常都会有多个 #include指令,它们用以包含常见的 #define语句和 extern声明,或从头文件中访问库函数的函数原型声明,比如 <stdio.h>

在大的程序中, #include指令是将所有声明捆绑在一起的较好的方法。它保证所有的源文件都具有相同的定义与变量声明,这样可以避免出现一些不必要的错误。很自然,如果某个包含文件的内容发生了变化,那么所有依赖于该包含文件的源文件都必须重新编译。

4.2 嵌套文件包含

嵌套 #include 文件的缺点

  • 它使得我们很难判断源文件之间的真正依赖关系 ;
  • 一个头文件可能会被多次包含;

为了说明这 种错误,看下面代码:

#include "x.h"
#include "x.h"

显然 , 这里文件 x.h 被包含了两次 。 没有人会故意编写这样的代码 。 但下面的代码

#include "a.h"
#include "b.h"

看上去没什么问题 。如果 a.hb.h 都包含一个嵌套的 #include "x.h",那么 x.h 在此处也同样出现了两次,只不过它的形式不是那么明显而已 。

多重包含在绝大多数情况下出现于大型程序中,它往往需要使用很多头文件,因此要发现这种情况并不容易。要解决这个问题 , 我们可以使用条件编译 。

#ifndef _HEADERNAME_H
#define _HEADERNAME_H
/* hdr.h 文件的内容放在这里 */
#endif

C++ 笔记(32)— 预处理、文件包含include、宏替换define、条件包含ifndef、define相关推荐

  1. js 用replace替换空格 替换空格包含换行符 替换空格不包含换行符

    第一种:替换所有的空格(包含换行符) let str = '123AD asadf asadfasf\n adf\n' let a = str.replace(/\s+/g," " ...

  2. C/C++黑魔法-利用include宏读文件

    本文介绍使用include宏读取文件内容并打印出来. include宏 C/C++中包含头文件命令,用于将指定头文件嵌入源文件中; 这里使用的include宏时将string.txt文件内容嵌入str ...

  3. 【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 )

    相关文章链接 : 1.[嵌入式开发]C语言 指针数组 多维数组 2.[嵌入式开发]C语言 命令行参数 函数指针 gdb调试 3.[嵌入式开发]C语言 结构体相关 的 函数 指针 数组 4.[嵌入式开发 ...

  4. jsp学习之包含——include

    http://blog.e23.cn/?uid-1255655-action-viewspace-itemid-134232 1.include: Include 指令: <%@ include ...

  5. c malloc 头文件_c++个人学习笔记——1.头文件声明

    简单介绍了C++头文件声明与C语言的差异,并对常见的部分头文件作了介绍. //C++中常用写法 最简单的C++程序往往是上面这样声明头文件. #include为C/C++中包含头文件命令,用于将指定头 ...

  6. 【文件包含、宏、条件编译】(学习笔记21--预处理命令)

    目录 文件包含 头文件 #include命令 多文件编译 宏 宏的定义 宏的取消定义 带参宏 将参数转换为字符串常量 参数结合 宏的嵌套使用 转换宏 条件编译 #if命令 #ifdef命令 #ifnd ...

  7. PHP 零基础入门笔记(8):PHP 文件包含include/require

    PHP 文件包含include/require 在一个 PHP 脚本中,去将另一个文件包含进来 文件包含的作用 实现代码共享重用,协作共同完成一件事 使用被包含文件中的内容:向上包含(所要) 自己的东 ...

  8. 标C编程笔记day04 预处理、宏定义、条件编译、makefile、结构体使用

    预处理:也就是包括须要的头文件,用#include<标准头文件>或#include "自己定义的头文件" 宏定义,如:#define PI 3.1415926 查看用宏 ...

  9. 宏替换、条件编译、头文件展开

    宏替换.文件编译和头文件的展开 程序执行的几个步骤: 1.预处理: ①将头文件展开②宏替换③条件编译④去掉注释 2.编译: ①语义语法纠错②将.c文件编译成汇编语言 3.汇编:将汇编语言变成二进制机器 ...

最新文章

  1. SLAM综述-Lidar SLAM
  2. WP老杨解迷:可知评论系统还能勾搭用户呢
  3. Eclipse : Unresolved inclusion
  4. python函数在传参的时候,到底在传些什么?
  5. 下标 获取字符_互联网人工智能编程语言Python的下标与切片详解
  6. easyUI 如何不跳转页面,只是加载替换center部分内容
  7. 关于以主机命名的网站集
  8. ajax跨域.pdf,探秘ajax跨域请求.pdf
  9. GoJS图表组件简介
  10. mysql服务器相互作用的协议_以下哪个不是与mysql服务器相互作用的通讯协议
  11. 虚拟化是什么,主要有哪些虚拟化技术?
  12. 乐视max2 刷入第三方recovery 然后刷入root 包 root
  13. 基于SSM开发的的小区物业管理系统小程序源码
  14. SAP SHIFT语法注意
  15. 《穷查理宝典》听书笔记
  16. MSP-EXP430F5529LP_GPIO
  17. #1506 : 投掷硬币
  18. iOS开发之模仿qq通讯录源代码!
  19. [2013.7.5新鲜出炉] Ubuntu12.04下载Android4.0.1源码全过程----------------折腾两天,终于下好,附若干问题解决
  20. 软考-软件设计师-复习整理

热门文章

  1. Linux shell 学习笔记(4)— linux 环境变量(全局变量、局部变量及变量持久化)
  2. python实现简单的用户密码登录控制(输入三次就锁定用户)
  3. 各bert 模型下载
  4. logistic 损失函数的解释
  5. GPU上稀疏矩阵的基本线性代数
  6. 模拟内存计算如何解决边缘人工智能推理的功耗挑战
  7. 加速针对COVID-19的医疗器械开发
  8. HTTP/HTTPS的请求和响应
  9. Swoole入门介绍
  10. Python 函数的可变参数(*paramter与**paramter)的使用