C++ 预处理指令介绍
预处理指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
C++支持很多预处理指令,比如 #include、#define、#if、#else、#line 等。
最常见的预处理有:
☆文件包含: #include 是一种最为常见的预处理,主要是做为文件的引用组合源程序正文。
☆宏替换(宏定义): #define,这是最常见的用法,它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。#define 预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:
#define 标识符 值
或
#define 标识符(参数表) 代码序列
其中参数表中的参数之间用逗号分隔,在代码序列中必须要包含参数表中的参数。在定义带参数的宏时,宏名与左圆括号之间不允许有空白符,应紧接在一起,否则变成了无参数的宏定义。带参数宏调用提供的实在参数个数必须与宏定义中的形式参数个数相同。
宏定义的有效范围称为宏名的作用域,宏名的作用域从宏定义的结束处开始到其所在的源代码文件末尾。宏名的作用域不受分程序结构的影响。如果需要终止宏名的作用域,可以用预处理指令#undef加上宏名。
宏名一般用大写字母,以便与变量名区别。如有必要,宏名可被重复定义,被重复定义后,宏名原先的意义被新意义所代替。
☆条件编译:一般情况下,在进行编译时对源程序中的每一行都要编译,但是有时希望程序中某一部分内容只在满足一定条件时才进行编译,如果不满足这个条件,就不编译这部分内容,这就是条件编译。 #if,#ifndef,#ifdef,#endif,#undef等是比较常见的预处理,主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。
下面举例解析之。
一、文件包含
预处理指令#include用于包含头文件,有两种形式:#include <>,#include ""。
#include <>
:这种形式通常用于包含标准库文件或系统提供的头文件。编译器将在标准库目录或系统路径中查找该头文件。如:#include <iostream> // 包含iostream头文件
#include ""
:这种形式通常用于包含用户自定义的头文件或其他项目相关的头文件。
尖括号形式表示被包含的文件在系统目录中。如果被包含的文件不一定在系统目录中,应该用双引号形式。
在双引号形式中可以指出文件路径和文件名。如果在双引号中没有给出绝对路径,则默认为用户当前目录中的文件,此时系统首先在用户当前目录中寻找要包含的文件,若找不到再在系统目录中查找。
对于用户自己编写的头文件,宜用双引号形式。对于系统提供的头文件,既可以用尖括号形式,也可以用双引号形式,都能找到被包含的文件,但显然用尖括号形式更直截了当,效率更高。
./表示当前目录,../表示当前目录的父目录。
提示:
一个 #include 命令只能包含一个头文件,多个头文件需要多个 #include 命令。
文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。
二、define 预处理
#define 预处理指令用于创建符号常量。例如:
#include <iostream>
using namespace std;
#define PI 3.14159
int main ()
{
cout << "PI的值:" << PI << endl;
return 0;
}
运行之,输出:
PI的值:3.14159
还可以使用 #define 来定义一个带有参数的宏,如下所示:
#include <iostream>
using namespace std;
#define MIN(a,b) (((a)<(b)) ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
cout <<"最小值为:" << MIN(i, j) << endl;
return 0;
}
运行之,输出:
最小值为:30
三、条件编译
条件编译命令可以使得编译器按不同的条件去编译程序不同的部分,产生不同的目标代码文件。条件预处理指令的结构与 if 选择结构很像。
指令意义
#if 表达式非零就对代码进行编译;
#ifdef 如果宏被定义就进行编译;
#ifndef 如果宏未被定义就进行编译;
#else 作为其它预处理的剩余选项进行编译;
#elif 这是一种#else和#if的组合选项;
#endif 结束编译块的控制。
常用格式
格式1:#if_#endif形式:
#if 整型常量表达式 或 #ifdef 宏名 或 #ifndef 宏名
程序段
#endif
如果整型常量表达式为真或者该宏名已定义或者该宏名未定义,则编译后面的程序段;否则就不编译,跳过这段程序。
注意,#if 后面跟的是“整型常量表达式”, 也就是说,表达式中不能包含变量,而且结果必须是整数。
格式2:#if_#else_#endif形式:
#if 整型常量表达式 或 #ifdef 宏名 或 #ifndef 宏名
程序段1
#else
程序段2
#endif
如果整型常量表达式为真或者该宏名已定义或者该宏名未定义,则编译后面的程序段1;否则编译后面的程序段2。
格式3:#if_#elif_#endif形式:
#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
.......
#elif 整型常量表达式n
程序段n
#endif
例子1:
#include <iostream>
using namespace std;
#define MAX 100
int main()
{
#if MAX>199
cout<<"正确"<< endl;;
#else
cout<<"错了"<< endl;;
#endif
return 0;
}
运行之,输出:
错了
例子2:
首先,说明Windows 有专有的预定义宏_WIN32,Linux 有专有的预定义宏__linux__,知道这些,就很容易看懂下面代码了:
#include <iostream>
using namespace std;
int main(){
#if _WIN32
cout<< "This is Windows!\n";
#else
cout<<"Unknown platform!\n";
#endif
#if __linux__
cout<<"This is Linux!\n";
#endif
return 0;
}
运行之,在我的电脑上(操作系统是Windows10)输出:
This is Windows!
C++ 提供的一些预定义宏:
macro value
__LINE__ 整数值,表示当前正在编译的行在源文件中的行数。
__FILE__ 字符串,表示被编译的源文件的文件名。
__DATE__ 一个格式为 "Mmm dd yyyy" 的字符串,存储编译开始的日期。
__TIME__ 一个格式为 "hh:mm:ss" 的字符串,存储编译开始的时间。
__cplusplus 整数值,所有C++编译器都定义了这个常量为某个值。
例如:
#include <iostream>
using namespace std;
int main ()
{
cout << "Value of __LINE__: " << __LINE__ << endl;
cout << "Value of __FILE__: " << __FILE__ << endl;
cout << "Value of __DATE__: " << __DATE__ << endl;
cout << "Value of __TIME__: " << __TIME__ << endl;
cout << "Value of __cplusplus: " << __cplusplus << endl;
return 0;
}
运行之,输出:
Value of __LINE__: 5
Value of __FILE__: C:\Users\Wang\Documents\预定义的宏
Value of __DATE__: Jul 24 2021
Value of __TIME__: 20:07:38
Value of __cplusplus: 199711
附录
关于C/C++中的预处理 - 知乎