Appearance
预处理器
预定义的符号
C
__FILE__ 进行编译的源文件名
__LINE__ 当前的行数
__DATE__ 编译时候的日期
__TIME__ 时间
__STDC__ 编译器是否遵循ANSI C
C
1 #include <stdio.h>
2
3 int main(void){
4 printf("file %s\n", __FILE__);
5 printf("line %d\n", __LINE__);
6 printf("date %s\n", __DATE__);
7 printf("time %s\n", __TIME__);
8 printf("stdc %d\n", __STDC__);
9
10
11 return 0;
12 }
result :
file main.c
line 5
date Jul 28 2022
time 18:08:31
stdc 1
#define
使用define你可以把文本替换到程序之中
如果define很长可以用 \ 来连接不同的行
C
#define DEBUG_PRINT printf("File %s line: %d:" \
" x=%d, y=%d, z= %d", \
__FILE__, __LINE__, x, y, z)
6 int main(void)
7 {
8 int x = 2;
9 int y = 1;
10 int z = 4;
11 DEBUG_PRINT;
12 return 0;
13 }
在这个宏定义调用的时候加了一个分号,所以在定义的时候最好不要加分号,如果插入宏定义的地方只能插入一条语句就会出问题,最好定义为函数
C
#define PROCESS_LOOP \
for(i = 0; i < 10; i++) \
{ \
sum += i; \
if(i>0) \
prod *= i; \
}
宏
C
#define name(parameter-list) stuff
parameter-list是一个用逗号分割的符号列表,括号左边必须和name紧邻
在使用的时候会原封不动的进行转换,所以要注意是不是会与周围的语句产生不同的含义
尽量少使用宏改变C语言的执行方式,让他看起来像别的语言的实现,会导致代码阅读不方便
define替换
在执行#define的替换的时候要涉及几个步骤
- 宏调用的时候检查是不是有地方包含#define定义的符号,有的话替换
- 替换的文本插入原来的位置
- 再次扫描
所以宏参数可以包含其他的宏符号,但是不能进行递归
在检查的时候不会检查字符串内部
插入到字符串的方法:
- 利用临近的字符串自动连接
C
1 #include <stdio.h>
2
3 #define PRINT(PORMAT, VALUE) \
4 printf("The value is "PORMAT"\n", VALUE)
5
6 int main(void)
7 {
8 PRINT("%d", 23);
9
10 return 0;
11 }
- 适用预处理器把宏定义转换为字符串 #argument 被翻译成"argument"
C
1 #include <stdio.h>
2
3 #define PRINT(FORMAT,VALUE) \
4 printf("The value of "#VALUE" is "FORMAT"\n", VALUE)
5 int main(void)
6 {
7 int x = 1;
8 PRINT("%d", x+3);
9 return 0;
10 }
result:
The value of x+3 is 4
##
构建一种不同的任务,他把两个符号连接到一起,构成一个新的符号
C
1 #include <stdio.h>
2
3 #define ADD_TO_SUM(sum_number, value) \
4 sum ## sum_number += value
5
6 int main(void)
7 {
8 int sum2 = 1;
9 ADD_TO_SUM(2, 45);
10 printf("%d\n", sum2);
11 }
宏与函数
宏频繁的使用于简单的运算,原因
- 从函数来回的时间可能比计算本身更长
- 函数必须声明为固定的类型,宏可以作用于不同的类型
缺点
- 使用的宏最后都会插入到程序之中,大量使用会使得程序变长
有一些任务函数不能实现
C
#define MALLOC(n, type) ((type *)malloc((n) * sizeof(type)))
有副作用的宏参数
如果宏参数在宏定义中出现多次,就有可能会产生副作用
C
#define MAX(a, b) ((a) > (b) ? (a) : b)
x = 5;
y = 8;
z = MAX(x++, y++);//这里较大的数进行了两次相加
//这个时候 x=6 y=10 z=9
类似的还有getchar函数
命名约定
由于使用的规则不同,最好让程序员知道的使用的是宏还是函数,但是两者的使用方法相同
- 所有的字母大写
不同之处
属性 | #define宏 | 函数 |
---|---|---|
代码长度 | 使用时会插入代码,程序大幅度增加 | 代码只出现在一个地方,每次都到同一个地方调用 |
执行速度 | 更快 | 存在函数的调用返回 |
操作符优先级 | 宏参数的求值是在周围的环境之中,会受到环境的影响,除非加上括号 | 函数的参数在函数调用的时候求值,求值的结果更容易预测 |
参数求值 | 参数每次使用于宏定义的时候都会重新求值,由于多次求值导致结果不可预测 | 在被调用之前只求值一次,多次调用不会多次求值 |
参数类型 | 宏与类型无关,只要参数合法就可以使用 | 函数的参数和类型有关,类型不同要用不同的参数 |
#undef
用于移除一个宏定义
命令行定义
许多编译器允许在命令行之中定义符号
- 使用-D命令可以在编译的时候创建变量
C
-Dname
-Dname=stuff
C
//main5.c
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int test[ARRAY_SIZE];//在之后才决定大小
6 printf("%d\n", sizeof(test));
7 return 0;
8 }
gcc -DARRAY_SZIE=5 main5.c
result :
20
- 使用-U会使得对应的变量被忽略
C
-Uname//实测不好用,只有在传参数定义宏的时候才会起作用
条件编译
C
#if constant-expression
stamtments
#endif
constant-expression由预处理器求值,如果求得的值是真的那就执行
C
#if constant-expression
stamtments
#elif constant-expression
stamtments
#else
stamtments
#endif
示例
C
#define DEBUG 1
#if DEBUG
printf("x=%d\n", x);
#endif
在早期的编译器中没有elif,但是可以使用if的嵌套来完成复杂的程序
是否被定义
C
#if define(symbol)
#ifdef symbol
#if !define(symbol)
#ifndef symbol
文件包含
预处理的时候删除这一条指令,并把文件中的内容取而代之
在这个时候整个头文件都会被编译一次,所以最好只放函数的声明,只存放必要的文件
使用#ifndef防止重复编译
嵌套文件包含
标准要求编译器最少支持8层以上的头文件包含
- 会导致不能判断真正的依赖关系,在编译的时候造成困难
- 头文件多次包含
C
#ifndef __AAAA
#define __AAAA
#endif
其他指令
- #error
C
#error text of error message//在编译的时候发出一个错误信息
示例
C
1 #error "111111"
2 int main(void)
3 {
4 return 0;
5 }
result:
main7.c:1:2: error: #error "111111"
1 | #error "111111"
| ^~~~~
- #line
C
#line number "string" //line行号,string可选文件名更改
示例
C
1 #include <stdio.h>
2
3 #line 100 "test110.c"
4
5 int main(void){
6 printf("file %s\n", __FILE__);
7 printf("line %d\n", __LINE__);
8 return 0;
9 }
result:
file test110.c
line 103
常用于把其他语言转换为C语言的代码之中,错误信息是源文件的行号和文件名
- #program 编译器处理一些设置,根据编译器的不同进行改变
#
单独一行,不加任何东西,用来突出显示其他行,在编译的时候会直接被删除