最近看到一些跟宏相关的代码,发现这玩意儿真是个神奇的东西...😂
嗯,本文的目的不是要介绍一些宏的神奇用法(这些日后再说),只是要简单记录一下跟宏相关的一些指令和操作。因为突然发现曾经学的语言书籍里,对宏的介绍都很少,就介绍个条件编译,几页纸就没了,结果一到正儿八经的工程项目中,宏的骚操作到处都有...
OK,闲话少说吧。
条件指令
首先基本的内容与书上的内容一致,也是条件编译指令。
本来想弄个表格一次性说明,结果发现如果想要解释的清楚一点,必须得写一点示例代码,那就一个一个来吧。
#if
#if
用来检查跟在其后的常量表达式是否为真,常见用法比如注释测试代码:1
2
3
4
5
6
7
8
9
10
int main() {
std::cout << "test code" << std::endl;
return 0;
}
#ifdef
#ifdef
用来检查宏是否已定义,比如我们可以用它来判断系统类型:1
2
3
4
5
6
7
8
9
10
int main() {
std::cout << "windows os" << std::endl;
return 0;
}
#ifndef
#ifndef
用来检查宏是否未定义,这与#ifdef
刚好相反,也可以观察到#ifndef
中间多了一个n
,应该就是单词not
的缩写吧。这个指令的用法就比较常见了,通常为了防止头文件重复包含,都会加上:1
2
3
4
#elif/#else
#elif
和#else
两个指令可以与#if
或#ifdef
配套使用,有点类似普通的if
语句。比如:1
2
3
4
5
6
7
std::cout << "linux os" << std::endl;
std::cout << "windows os" << std::endl;
std::cout << "other os";
#endif
#endif
就没啥说的了,条件编译指令的结束指令。
操作指令
第二块要介绍的是操作指令。
#define
#define
用来定义宏,比如:1
2
#undef
#undef
用来取消宏定义,比如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
std::cout << "windows os" << std::endl;
std::cout << "other os";
return 0;
}
/*
output:
other os
*/
在上述代码中,我们取消了_WIN32
这个宏定义,结果程序最后输出了other os
。
#defined()
#defined()
用于在条件编译指令中检查宏,比如我们想检查特定的宏是否被定义:1
2
3
4
5
6
7
8
9
10
int main() {
std::cout << "MY_MACRO" << std::endl;
return 0;
}
注意这个宏无法单独使用,只能与条件编译指令一起使用。有了它之后,可以把条件编译写的更强大,也更...复杂...
#
#
可以把传入的宏参数,转化为字符串字面量,比如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
std::cout << TOSTRING(123) << std::endl;
std::cout << TOSTRING(abc) << std::endl;
std::cout << TOSTRING(1a2b3c) << std::endl;
return 0;
}
/*
123
abc
1a2b3c
*/
我们也很容易发现,这个宏操作的原理其实就是,在预处理阶段,进行文本替换的同时给宏参数加上了一堆双引号,也即:1
2
3TOSTRING(123) -> "123"
TOSTRING(abc) -> "abc"
TOSTRING(1a2b3c) -> "1a2b3c"
同时,#
指令后面可以不跟任何内容,只作为占位符使用。
##
##
用来连接宏参数,比如:1
2
3
4
5
6
7
8
9
10
11
12
int main() {
// CONCAT(1, 2) -> 12
int a = CONCAT(1, 2);
std::cout << a << std::endl;
return 0;
}
其实就是删除了宏参数之间的逗号。
特殊指令
没啥好说的,这部分就是一些特殊的条件指令。
#include
最先出场的就是#include
,这个没啥好说的了,大家写的第一行代码。
#error/#warning
#error
用来在预处理阶段强制触发编译错误并输出自定义错误信息,比如:
1 |
|
#warning
用法与#error
类似,但是触发的是警告,需要注意的是#warning
不属于 C/C++ 标准,但是 GCC、MSVC 和 clang 都支持。
#line
#line
用于修改编译器内部记录的行号和文件名(主要用于诊断定位),比如:1
2
#pragma
#pragma
用来向编译器传递非标准指令,比如:1
2
另外,关于这个宏还需提一下,在 C99 和 C++11 中引入了_Pragma
操作符来完成这部分功能。对应的用法:1
2_Pragma("once")
_Pragma("pack(1)")
也就是说,#pragma
和_Pragma
用法是相同的,但是_Pragma
有个很显然的优点就是,它可以与其他宏指令一起使用,但#pragma
只能单独成行(有点莫名的孤独感出现了~🤣),比如:
1 | // 使用 #pragma(无法在宏中直接使用) |
标准预定义宏
最后,再整理一下常用的预定义宏,留作日常使用。
PS:严重怀疑这玩意儿最初一定是编译器开发人员为了给自己省事开的后门😂。
标准预定义宏
宏名称 | 功能描述 | 示例值 |
---|---|---|
__FILE__ |
当前源文件的完整路径(字符串) | "src/main.cpp" |
__LINE__ |
当前代码行的行号(整数) | 42 |
__DATE__ |
编译日期(格式 “Mmm dd yyyy”) | "Dec 14 2023" |
__TIME__ |
编译时间(格式 “hh:mm:ss”) | "15:30:45" |
__func__ (C99/C++11) |
当前函数名(字符串) | "main" |
__cplusplus |
C++ 语言版本标识(仅在 C++ 中定义) | 199711L (C++98)201703L (C++17) |
__STDC__ |
标准 C 编译器(值为 1) | 1 |
__STDC_VERSION__ |
C 标准版本(C 编译器) | 199901L (C99) |