引言
在C++中有很多的预处理指令,但是初学者一般见的不是很多,这两天在敲项目的时候见到了很多以前从没见过的写法,查了很多的资料写了这篇文章。
ifdef,ifndef,define和endif
预处理指令就可以看作是文本替换!!!
这是非常常用的C++预处理指令,特别是在头文件的编写中。
这里写一段代码来展示下它的用法:(这里我们先在头文件MyHeader.h中编写以下内容)
#ifndef MYHEADER_H
class A{};
#endif
#ifdef MYHEADER_H
class B{};
#endif
然后我们在另一个文件中使用这个这个头文件:
#include"MyHeader.h"
这样在包含头文件的时候编译器就会对这个预处理指令进行一个判断:如果条件为真,就会包含所包含的直到对应的endif的所有内容。在本例中:ifndef为true,就会将A类链接;但是ifdef为false,所以B类没有被包含。
现在我们在使用之间加上一个预处理指令,更改为以下代码:
#define MYHEADER_H
#include"MyHeader.h"
现在在包含头文件之前就已经存在了MYHEADER_H,所以包含情况就改变了:此时是B类被包含而A类未被包含。
更多花样
这里我想要再说说define,因为它的花样真的很多。
在先前我们已经说过它其实就可以立即为是文本替换:
#define MAX 100
#include<iostream>
int main(){
std::cout << MAX << std::endl;
}
其实就是在使用的时候将MAX替换为100,但是这种简单的define其实是不被建议的,我们更应该使用const变量来替代宏定义,上述代码更标准的写法是:
#include<iostream>
//用来替代define
const int MAX = 100;
int main(){
std::cout << MAX << std::endl;
}
define应该被用来做更复杂的替代操作。这里我给出我遇到的一段令人“眼花缭乱‘的代码:
#define LOG_BASE(level, format, ...)\
do{\
Log* log = Log::Instance();\
if(log->IsOpen() && log->GetLevel() <= level){\
log->write(level, foramt, ##__VA_ARGS__);\
log->flush();\
}\
}while(0);
这么一长串的都是宏定义。这里慢慢来解释其中看不懂的内容。
宏名
首先是宏名,在这里宏名是LOG_BASE(level, format, …)。是的,这么长一串全是,若是在正文中使用它,它将会被替换为宏的内容。
我们会发现:在它的后面有一个反斜杠"\",这就是我们接下来要讲的内容。
反斜杠"\"
在很多时候,我们给宏的内容可能很多,但是宏定义本质上只能放在一行,为什么上面的代码能放这么多行呢?正是因为这个反斜杠。它的意思是将下一行的内容连接至本行,如果我们不使用它的话,那么我就必须将所有的内容挤在一行,很不美观、可读性也非常的差。
“__VA_ARGS__”
这个是个宏定义,我就不多说了,它是C语言的可变参数列表,需要包含头文件stdarg.h,对应着宏名里面的“…”,也就是省略形参,除了前两个已知参数之外,它可以包含任何类型的参数。
“##”
这是双井号,在__VA_ARGS__的前面出现了它,它的本意是将两个标识符连接为一个新的标识符,这么说可能有点抽象,这里就给段代码来解释下吧:
//定义
#define CONNECT(a,b) a##b
#include<iostream>
int main(){
const int num1 = 1;
const int num2 = 100;
//使用
std::cout << "CONNECT(num, 1) = " <<CONNECT(num, 1) << std::endl;
}
我们看看它的输出是什么:
CONNECT(num, 1) = 1
也就是说:CONNECT(num, 1)被替换为num1,即我之前所说的连接操作。需要注意的是:不能使用它将两个变量进行连接!!!,这里也给出一段错误代码:
#define CONNECT(a,b) a##b
#include<iostream>
int main(){
const int num1 = 1;
const int num2 = 100;
//使用
std::cout << "CONNECT(num, 1) = " <<CONNECT(num1, num2) << std::endl;
}
这样很显然就是一段未定义的行为,程序也会报错无法运行。
“#”
这个符号虽然没有出现在上面的代码中,但是因为它常用所以我在这里也说说:它的作用可以理解为toString(),即:将内容转换为字符串。示例如下:
#define PRINT(var) printf(#var"=%d\n", var)
const int a = 10;
PRINT(a);
这里所作的操作就是**接收一个变量后将接收的变量名转换为字符串类型后打印==。
“#@”
前面说到"##"是toString()操作,而这个是toChar()操作,用法跟前者差不多,我就不多说了。
pragma once
这个预处理指令它不是C++原生的预处理指令,但是它在很多编译器中都能使用,例如:VS。它能够替代define和endif的搭配,使文件只被包含一次,一般在头文件中使用:
//在头文件的开始
#pragma once
...//头文件的内容