欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

预处理指令

程序员文章站 2022-03-15 19:37:38
...

文章目录

前题

       这里仅讨论预处理在C++中的应用。
       如何知道你的代码被预处理成了什么样子?用g++ -E file.cpp -o file.txt

正文

define

       宏是一个很庞大的话题,其本质是字符串替换。

无参形式
       #define应该是最常见的宏定义了,它可以自定义一些标识符,来代替一些字符串,来在一定程度上简化代码。
       例如#define LL long long,后面就可以用LL来代替long long,这等价于typedef long long LL;。这就是宏定义的无参形式。

带参形式
       下面就是很多人认为的宏函数

#define max(a,b) a>b?a:b

       可是按照上面的方法是有问题的,例如!max(a,b)会被翻译成(!a)>b?a:b,而不是!(a>b?a:b)。因此,正确的写法是这样的。

#define max(a,b) ((a)>(b)?(a):(b))

###
       #:字符串化一个宏参数,即在参数名字前后加上"
       有的人想,我能不能构造这样一个函数f(x),输出x=...呢?当然可以!

#define f(x) printf(#x"=%d",x)

       除了#,还有##符号,它拼接宏参数和另一个符号,即连接两个符号生成一个新的符号。例如:

#define f(y) var_##y
int main(){
	int f(d);
}

会被解释成:

int main(){
	int var_d;
}

变参宏
       有的竞赛不能使用freopen,而只能使用fopenfprintf,而这样也导致了一定程度上的编程困难,能不能把printf(format,...)变成fprintf(fp,format,...)呢?好在系统提供了预定义宏__VA_ARGS__,因此我们可以这样:#define printf(format,...) fprintf(fp,format,__VA_ARGS__)唯一的缺点是printf必须至少有一个参数,同理,scanf也可以这么弄:#define scanf(format,...) fscanf(fp,format,__VA_ARGS__)
       但是必须注意,当__VA_ARGS__作为宏实参再次被传入另一个宏函数的时候,可能会被解释为一个参数(例如在VC中)

#define ATTR_1(arg) printf(arg);
#define ATTR_2(arg, ...) ATTR_1(arg) ATTR_1(__VA_ARGS__)
#define ATTR_3(arg, ...) ATTR_1(arg) ATTR_2(__VA_ARGS__)
int main(){
	ATTR_3("123\n","456\n","789\n");
}

       可能会被解释成:

int main(){
	printf("123\n"); printf("456\n","789\n");
}

       因为后面的"456\n","789\n"被认为是同一个参数,那有什么解决方案吗?答案是借助辅助宏。

#define ATTR(args) args
#define ATTR_1(arg) printf(arg);
#define ATTR_2(arg, ...) ATTR_1(arg) ATTR(ATTR_1(__VA_ARGS__))
#define ATTR_3(arg, ...) ATTR_1(arg) ATTR(ATTR_2(__VA_ARGS__))

       这样就可以了。但DEV_C++却并不会有这个问题,因此最好加上。
prescan
       当一个宏参数被放进宏体时,这个宏参数会首先被全部展开。当展开后的宏参数被放进宏体时, 预处理器对新展开的宏体进行第二次扫描,并继续展开。例如:

#define put(x) printf("%d",x)
#define sign(x) INT_##x
int INT_1;
put(sign(1)); 

       会被替换为

int INT_1;
printf("%d",INT_1);

       但是有例外——当PARAM宏里对宏参数使用了#或##,那么宏参数不会被展开:

#define PARAM(x) #x 
#define ADDPARAM(x) INT_##x 

       PARAM(ADDPARAM(1)); 将被展开为"ADDPARAM(1)"。使用这么一个规则,可以创建一个很有趣的技术:打印出一个宏被展开后的样子,这样可以方便你分析代码:

#define str(x) TO_STRING(x)
#define TO_STRING(x) #x

       这样可以用printf("%s",str(宏)),来输出展开后的样子。

内置宏
系统提供了一些内置宏。

  1. __func____FUNCTION__:该宏所在的函数名。
  2. __LINE__:该宏所处的行数。
  3. __DATE__:该宏所在函数的当前编译日期。
  4. __TIME__:该宏所在函数的当前编译时间。
  5. __FILE__:该宏所在的程序文件的名字。

纯定义
       除了无参形式和带参形式,还有很多人都不知道的一个用法——定义标识符。

#define Sign

它的具体用处会在#if处有讲解。


include

       几乎每条程序都不可避免地使用到#include,可你真的知道它的意思吗?

#include<file>
       这就是最常见的include命令。它表示引用编译器的类库路径里面的头文件,例如#include<cstdio>

#include"file"
       我敢肯定很多人(不是全部)都不知道include有这种用法。它和#include<file>的区别是什么呢?它引用的是你程序目录的相对路径中的头文件。
       比如,我想从网上下载了一个头文件,名为frac.h,那么想要把在程序同一目录下的fun.h文件导入代码,就可以用include"frac.h"。但要注意,如果#include"cstdio"没有找到cstdio这个文件,那么它将去编译器的类库路径里面找。

本质
       事实上,它不仅仅可以包含.h文件和C++的无后缀文件,它还可以包含任何类型的文件,甚至是.txt
       看起来“引用”这个概念很玄虚,很高级,其实编译器干的只是一件是个人都会做的事——头文件展开。其实就是把cstdio的内容原封不动地放到你的代码中。
       那事情就简单了,甚至我们都可以自己写头文件!

//mains.cpp
int main(){
	printf("Hello Include!");
}
//run.cpp
#include<cstdio>
#include"mains.cpp"

把两份代码按照命名放在相同目录下,然后编译run.cpp,你会发现完美运行,其实编译器就仅仅是把文件原封不动地放到你的代码中。

相关标签: