C++随笔-预处理器

参考原文:https://blog.csdn.net/huang_xw/article/details/7648117

这里只讨论处理以#开头的预处理的编译指令的预处理器

预处理器是在程序源文件被编译之前根据预处理指令对程序源文件进行处理的程序。预处理指令一般是无法直接被编译和链接的,则需要由预处理器来进行操作。而在C++中,可以使用预处理指令和具有预处理的功能。预处理中往往会对原文件进行文本替换、添加等操作。

C++中提供了三种预处理功能:

1.文件包含

预处理指令#include用于包含头文件,有两种形式:#include <xxx.h>,#include "xxx.h"。
尖括号形式表示被包含的文件在系统目录中。如果被包含的文件不一定在系统目录中,应该用双引号形式。
在双引号形式中可以指出文件路径和文件名。如果在双引号中没有给出绝对路径,则默认为用户当前目录中的文件,此时系统首先在用户当前目录中寻找要包含的文件,若找不到再在系统目录中查找。
对于用户自己编写的头文件,宜用双引号形式。对于系统提供的头文件,既可以用尖括号形式,也可以用双引号形式,都能找到被包含的文件,但显然用尖括号形式更直截了当,效率更高。

2.宏替换

  1. 宏定义:宏定义的作用一般是用一个短的名字代表一个长的代码序列。分为带参宏定义和无参宏定义
  2. 宏展开:预处理器在处理宏定义时,会对宏进行展开(即宏替换)。宏替换首先将源文件中在宏定义随后所有出现的宏名均用其所代表的代码序列替换之,如果是带参数宏则接着将代码序列中的宏形参名替换为宏实参名。宏替换只作代码字符序列的替换工作,不作任何语法的检查,也不作任何的中间计算,一切其它操作都要在替换完后才能进行。如果宏定义不当,错误要到预处理之后的编译阶段才能发现。源代码中的宏名和宏定义代码序列中的宏形参名必须是标识符才会被替换,即只替换标识符,不替换别的东西,像注释、字符串常量以及标识符内出现的宏名或宏形参名则不会被替换。即这部分完成的是纯粹的文本替换。
  3. 宏的独立性:在宏定义中说过,宏名和宏形参名所代表的内容及意义在宏展开前后必须一直是独立且保持不变的,不能分开解释和执行。其原因如下,在宏调用时,用宏定义的代码序列替换宏名,用宏实参名替换宏形参名。替换后,宏定义的代码序列就与源文件中相邻的代码自然连接,宏实参名也与代码序列中相邻的代码自然连接,宏定义的代码序列和宏实参名的独立性就不一定依旧存在。例如:#define SQR(x) x*x,希望实现表达式的平方计算。对于宏调用p=SQR(y),能得到希望的宏展开p=y*y。但对于宏调用q=SQR(u+v),得到的宏展开是q=u+v*u+v。显然,后者的展开结果不是程序设计者所希望的。为能保持宏实参名替换后的独立性,应在宏定义中给形式参数加上括号。进一步,为了保证宏名调用的独立性,作为算式的宏定义代码序列也应加括号。SQR宏定义改写成#define SQR(x) ((x)*(x))才是正确的宏定义。
  4. 宏和函数的区别:函数调用在程序运行时实行,而宏展开是在编译的预处理阶段进行;函数调用占用程序运行时间,宏调用只占编译时间;函数调用对实参有类型要求,而宏调用实在参数与宏定义形式参数之间没有类型的概念,只有字符序列的对应关系;函数调用可返回一个值,宏调用获得希望的代码序列。另外,函数调用时,实参表达式分别独立求值在前,执行函数体在后。宏调用是实在参数字符序列替换形式参数。
  5. 预定义宏:已经封装好的宏定义,例如:__DATE__: 表示当前所在源文件的编译日期;__TIME__: 表示当前所在源文件的编译日期;__FILE__: 表示当前所在源文件名,且包含文件路径…

3.条件编译指令

一般情况下,在进行编译时对源程序中的每一行都要编译,但是有时希望程序中某一部分内容只在满足一定条件时才进行编译,如果不满足这个条件,就不编译这部分内容,这就是条件编译。条件编译主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到多个版本控制、防止对文件重复包含的功能。#if、#ifndef、#ifdef、#else、#elif、#endif是比较常见条件编译预处理指令,可根据表达式的值或某个特定宏是否被定义来确定编译条件。其实可以理解为#define相当于获得了一个标志符,当完成相应的操作时,通过这个宏定义获取这个标志符,来表示已经完成了相应的操作。再利用条件编译指令,可以通过判断是否存在标志符来决定是否执行相关的操作。通常使用文件名的作为宏定义,即作为标志符,用于判断是否已经包含了某个文件。当包含了某个文件时,会使用宏定义#define创建一个标志符。

猜你喜欢

转载自blog.csdn.net/weixin_39721347/article/details/86686605
今日推荐