宏定义、预处理指令
预处理是在编译之前的处理,C 语言预处理器执行宏替换、条件编译和文件包含。通常采用以“#”为行首的提示。下面是 C 语言预处理的应用场合:
三字母词
C 源程序的字符集被包含在 7 位的 ASCII 字符集中,但是它是 ISO 646-1983 Invariant Code Set 的超集。为了让程序可以在缩减集(reduced set)中呈现出来,下面的三字母词(Trigraph Sequences)会被替换成相应的单字符。
三字母词 | 单字符 |
---|---|
??= | # |
??/ | \ |
??' | ^ |
??( | [ |
??) | ] |
??! | ` |
??< | { |
??> | } |
??- | ~ |
替换发生在任何其他处理之前。
例如:如果你尝试打印字符串"what??!"
printf("what??!\n");
会得到字符串"what|"
。
注意:由于编译器对 ANSI C 的支持不一样,有些编译器会把三字母词当普通字符处理,你需要给编译选项加上“
-trigraphs
”
行拼接
以反斜杠 \
结尾的行会把该行和下一行拼接成一行(预处理器做的工作就是把该反斜杠 \
和接着的换行字符 \n
删除)。
因此,我们把反斜杠 \
称为续行符。
例如你可以这样写
/\
* This is a legal
multi-line comment. *\
/
宏定义和展开
简单宏展开
简单宏替换使程序中能用一个标识符来表示一个单词串,指令形式为:
#define 标识符 单词串
标识符(称为宏名)被定义为后面的单词串;单词串(简称串)是任意以换行结束的用于替换程序中该标识符的正文。如果串太长需要写成多行,则除了最后一行外每一行末尾都要有一个续行符(即添加一个“\
”后回车)。
宏名一般用大写字母,以便与变量名区别开来。
注意:字符串常数中出现的与宏名相同的字符串不在替换之列。
宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef
命令。
带参数的宏替换
预处理指令的形式为:
#define 宏名(参数列表) 单词串
“宏名(标识符,标识符,...,标识符)
”是被定义的宏,()
中的标识符是宏的形式参数;宏名与其后的 ()
之间不能有空白符。
例如:
#define max(a,b) ((a)>(b)? (a): (b))
在编译预处理时,将源程序中所有宏名替换成字符串,并且将 字符串中的参数 用 宏名右边参数列表 中的参数替换。
操作符 #
和 ##
操作符 #
把其后的串变成双引号包围的串;
操作符 ##
把两个标志符拼在一起,形成一个新的标识符
#include <stdio.h>
#define str(expr) #expr
#define cat(x, y) x##y
int main()
{
int ab = 12;
printf(str(hello world !));
// Expand to: printf("hello world!");
printf("ab=%d\n", cat(a, b));
// Expand to: printf("ab=%d\n", ab); 输出 ab=12
}
宏替换时的顺序
宏的解开不像函数,由里到外。
- 在
""
内的宏名或宏参数名不被替换 - 一个带参数的宏内部调用另一个宏,参数也是一个宏,则先替换外层的宏,再替换外层宏的参数,最后替换内层宏。
#include <stdio.h>
#define f(a,b) a##b
#define g(a) #a
#define h(a) g(a)
int main()
{
printf("%s\n", h(f(1,2))); // print: 12
printf("%s\n", g(f(1,2))); // print: f(1,2)
return 0;
}
在编写带参数的宏替换指令时,推荐的做法时将单词串中的每一个参数都用 ()
括起来,整个表达式也要用 ()
括起来;否则,替换结果可能不是你想要的,例如:
#define sqr(x) x * x
如果程序中的宏的引用形式为:sqr(3.0+1.0);
,那么经预处理后会被替换为 3.0 + 1.0 * 3.0 + 1.0
,结果与 (3.0+1.0)*(3.0+1.0)
不等价。
上述例子的正确写法:
#define sqr(x) ((x) * (x))
但正所谓,假的总是假的,即使宏多么像函数,它依旧不是函数,如果真的把它当成函数,则会在某些时候错的摸不着头脑。
还是一个经典的例子,比较大小:
#include <stdio.h>
#define CMP(x, y) (x > y ? x : y)
int main()
{
int x = 100, y = 200;
int result = CMP(x, y++);
printf("x = %d, y = %d, result = %d\n", x, y, result);
return 0;
}
执行这部分代码,会输出什么呢? 答案是,不知道!至少 result
的值我们无法确定,我们将代码展开得到:
int result = (x > y++ ? x : y++);
在 C 语言标准中,对于一个确定的程序语句,一个对象只能被修改一次,超过一次的结果则是未定的,由编译器决定。
由此可看出,宏的使用也是有风险的,所以虽然宏强大,但是依旧不能滥用。
可变参数宏
https://www.cnblogs.com/wanghetao/p/4492334.html
预处理指令
文件包含
代码 | 说明 |
---|---|
#include <filename> | 编译器将在系统的头文件目录中搜索 |
#include "filename" | 编译器首先查找当前工作目录或源代码目录,然后再在标准位置查找 |
条件编译
条件编译指令格式如下:
{if-line}
...
[#elif 常量表达式]
...
[#else]
...
#endif
if-line
为下面中的任意一种形式:
#if 常量表达式
#ifdef 标识符
#ifndef 标识符
defined
操作符用来判断标识符是否定义过。形式如下:defined 标识符
或 defined (标识符)
。
下面左边的写法等价于右边的写法:
写法 A | 写法 B |
---|---|
#ifdef 标识符 | #if defined 标识符 |
#ifndef 标识符 | #if ! defined 标识符 |
可以在编译的时候通过如添加编译选项的方式来控制编译出的代码,而无需修改源文件。
C 和 C++ 混合编程的情况
经常能在源代码中看见 extern "C"
这样的身影,这是做什么的?
这是为了混合编程而设计的,常出现在 C++ 的源代码中,目的是为了让 C++ 能够成功的调用 C 的标准或非标准函数。
#if defined(__cplusplus) || defined(_cplusplus)
extern "C" {
#endif
/* 主体代码 */
#if defined(__cplusplus) || defined(_cplusplus)
}
#endif
这样就能在 C++ 中调用 C 的代码了。
在 C 中调用 C++ 的函数需要注意,不能使用重载功能,否则会失败,原因详见 C++ 对于重载函数的实现
Pragmas
#pragma
是编译程序实现时定义的指令,它允许由此向编译程序传入各种指令。例如,一个编译程序可能具有支持跟踪程序执行的选项,此时可以用 #pragma
语句选择该功能。编译程序忽略其不支持的 #pragma
选项。#pragma
提高 C 源程序对编译程序的可移植性。
预定义宏
C 语言规范了 5 个固有的预定义宏,他们分别是
宏名 | 说明 |
---|---|
__LINE__ | 当前行号,十进制常量 |
__FILE__ | 正在编译的文件的文件名,字符串常量 |
__DATE__ | 编译的日期字符串,形如 "MMM DD YYYY" 的字符串常量 |
__TIME__ | 编译的时间字符串,形如 "HH:MM:SS" 的字符串常量 |
__STDC__ | 如果 __STDC__ 的内容是十进制常数1,则表示编译程序的实现符合标准C |
当前行及当前文件
#include <stdio.h>
#define DEPAKEGE(X) #X
#define PAKEGE(X) DEPAKEGE(X)
#define WHERE_AM_I "Line: " PAKEGE(__LINE__) " in " __FILE__
int main() {
puts(WHERE_AM_I);
}