起因
之前对预处理、宏定义、条件编译,以及文件包含做了个小小总结。涉及而又常用的预处理相关知识还有一些,这次仍然粗略记下。
预处理操作符
C 语言中有两个预处理操作符:#(字符串化运算符)、##(记号粘贴运算符),都可以在 #define
中使用。
字符串化运算符
“#” 的操作数必须是宏替换文本中的形参。其作用是:用双引号 将这个形参对应到的实参 包起来。来一个简单的示例:
#include <stdio.h>
#define GREET(name) ("Hello " #name)
int main()
{
printf("%s\n", GREET(Guan));
}
// 输出:
Hello Guan
尤其注意,这里的 Guan 没做定义或申明,但可以直接使用。再来看个列子:
#include <stdio.h>
#define GREET(name) ("Hello " #name)
int main()
{
char* Guan = "Zhong"; // Guan 是一个 char* 的指针
puts(Guan);
printf("%s\n", GREET(Guan));
}
// 输出:
Zhong
Hello Guan
也就是说,操作符 # 不管操作数是不是变量,总之直接将它字符串化,而且是将操作数的表面字符串化。我们再看看上述代码预处理之后的结果:
// example.i
...
int main()
{
char* Guan = "Zhong";
puts(Guan);
printf("%s\n", ("Hello " "Guan"));
// 编译器会自动合并紧邻的字符串 =》 "Hello Guan"
}
这样做有什么好处吗?那必然是有的,至少可以简化代码。像下面这样:
#include <stdio.h>
#define PRINT(fmt, value) printf("Value of " #value " is " fmt "\n", value)
int main()
{
int num = 100;
PRINT("%d", num); // 使用 # 操作符
printf("Value of num is %d\n", num); // 不使用操作符
}
// 输出:
Value of num is 100
Value of num is 100
记号粘贴运算符
“##” 的作用是把左右操作数结合起来,作为一个记号。你需要确保这个合成的记号在之前已经存在,可以是宏,也可以是变量。
#define TEXT_A "hello world"
#define msg (TEXT_ ## A)
int main()
{
printf("%s\n", msg); // 输出:hello world
}
其实就是 “TEXT_ ## A => TEXT_A” ,预处理器发现:欸哥们(TEXT_A)在呀!于是又把宏 TEXT_A 的文本拿来替换。倘若 TEXT_A 不存在,编译器会报错。
test.c:106:14: error: ‘TEXT_A’ undeclared (first use in this function)
#define msg (TEXT_ ## A)
^
test.c:110:20: note: in expansion of macro ‘msg’
printf("%s\n", msg);
^
test.c:106:14: note: each undeclared identifier is reported only once for each function it appears in
#define msg (TEXT_ ## A)
^
test.c:110:20: note: in expansion of macro ‘msg’
printf("%s\n", msg);
^
我们来看看预处理器处理后的结果:
// example.i
...
int main()
{
printf("%s\n", (TEXT_A));
}
显然,预处理器已经尽力了,只是它无力回天,毕竟代码中 TEXT_A 什么也不是。但从预处理后的结果来看,如果 TEXT_A 是个变量,似乎程序就可以执行下去。
#define msg (TEXT_ ## A)
int main()
{
char* TEXT_A = "hello world";
printf("%s\n", msg); // 输出:hello world
}
另一个需要注意的是,操作符 ## 左右两边的操作数不能是宏,但可以是形参。
// 如果是 宏
// example.c
#define TEXT_ 123
#define A 4
#define msg (TEXT_ ## A)
int main()
{
printf("%s\n", msg);
}
/**************************/
// example.i
...
int main()
{
printf("%s\n", (TEXT_A)); // TEXT_ 和 A 并没有被前边的宏替换
}
// 如果是 形参
// example.c
#define msg(TEXT_, A) (TEXT_ ## A)
int main()
{
printf("%s\n", msg(123, 4));
}
/**************************/
// example.i
...
int main()
{
printf("%s\n", (1234)); // TEXT_ 和 A 被实参替换
}
同操作符 “#” 一样,“##” 也不能转换 C 代码中的变量。其实这是有道理的,预处理只做预处理该做的事,不会对代码做分析。所以预处理阶段并不知道谁是变量、谁不是变量。
// example.c
#define msg(TEXT_, A) (TEXT_ ## A)
int main()
{
char* a = "123";
char* b = "4";
printf("%s\n", msg(a, b));
}
/**************************/
// example.i
int main()
{
char* a = "123";
char* b = "4";
printf("%s\n", (ab)); // 而不是 "1234"
}
预定义宏
一些常用的预定义宏,在代码中可以直接使用。
__FILE__ 源文件名
__LINE__ 文件当前行
__DATE__ 文件被编译日期(Mmm dd yyyy)
__TIME__ 文件被编译时间(hh:mm:ss)
__func__ 当前所在函数名
示例:
#include <stdio.h>
#define LOG(fmt, ...) printf("%s %s | %s %s %d: " fmt "\n", \
__DATE__, __TIME__, \
__FILE__, __func__, \
__LINE__, __VA_ARGS__)
void sum(int a, int b)
{
int result = a + b;
if (result >= 10) {
LOG("%s", "超出限定值,最后结果必须小于10");
}
}
int main()
{
sum(11, 9);
}
// 输出:
Apr 20 2019 16:02:44 | example.c sum 12: 超出限定值,最后结果必须小于10
当然,预定义宏不止这些。
其他指令
一些常见的其他预处理指令。
error
#ifndef HELLOW
#error "请先定义宏 HELLOW"
#endif
int main()
{
;
}
预处理时出错:
Guan@Orchard:~/CC++/19/src/demo$ gcc -E -o i example.c
example.c:3:6: error: #error "请先定义宏 HELLOW"
#error "请先定义宏 HELLOW"
^
pragma
#pragma
可以做一些 note 这样的提示信息,但考虑到不同机器对它有不同的兼容性,建议少用。
#ifndef HELLOW
#pragma message("你是不是忘记定义宏 HELLOW 了")
#endif
int main()
{
;
}
预处理时没有信息,但编译时会有提示:
Guan@Orchard:~/CC++/19/src/demo$ gcc example.c
example.c:3:13: note: #pragma message: 你是不是忘记定义宏 HELLOW 了
#pragma message("你是不是忘记定义宏 HELLOW 了")
^
同 #error
最大的区别是,#pragma
提示之后仍可以生成可执行文件,但 #error
不可以。
line
#line
可以改变默认的行号和文件名。其语法格式为:#line line_number filename
,filename 可以省略,但 line_number 不行。
示例:
#include <stdio.h>
int main()
{
printf("当前行号:%d\n", __LINE__);
#line 1 // 改变下一行代码的行号
printf("当前行号:%d\n", __LINE__);
printf("当前行号:%s\n", __FILE__);
#line __LINE__ "my.c"
printf("当前行号:%s\n", __FILE__);
}
// 输出:
当前行号:5
当前行号:1
当前行号:example.c
当前行号:my.c
感谢
- 参考《C 语言核心技术(第 2 版)》
- 参考 海同网校王正平老师的 C 语言之预处理
还不快抢沙发