C语言基础学习---预处理器

14. 预处理器

14.1 预处理器工作原理

预处理器的行为是由「预处理指令(#开头的一些命令)」控制的。目前来说碰到的两种。

定义一个宏---用于代替其他东西的名字,例如常量或者表达式。
1
2
3
4
5

``` #include``` 指令用于告知处理器打开一个特定的文件,将他的内容作为正在编译的文件一部分包含进源代码中。

```shell
cc -E xxx.c -o xxx.c

这个命令可以生成预处理后的文件,可以打开看到上述两个预处理命令都被删除,然后会增加一些代码。

预处理器的输入是一个c文件,这个文件可能包含预处理指令,预处理器会执行这些指令,并在处理过程中删除这些指令。预处理器的输出是另一个c文件,这个文件再被交给编译器,进行后面的步骤。

14.2 预处理指令

常用的三种类型预处理指令

  1. 宏定义

    #define 定义宏 #undef 删除宏

  2. 文件包含

    #include 包含指定文件类容

  3. 条件编译

    #if #ifdef #ifndef #elif #else #endif 指令根据预处理器可以测试的条件哎确定是将这一段文本包含到程序中还是将其排除在程序之外。

适用于所有指令的规则

  1. 指令都以 # 开头
  2. 指令的符号之间可以插入任意数量的空格或者回评制表符
  3. 指令总是在一个换行符处结束,除非明确的知名要延续
  4. 指令可以出现在程序中的任何地方
  5. 注释可以与指令放在同一行

14.3 宏定义

两种类别,简单的宏 和 带参数的宏。

14.3.1 简单的宏

简单的宏「C标准中称为 对象式宏」的定义格式:

1
#define 标识符 替换列表

替换列表是一系列预处理记号,可以包括标识符、关键字、字符常量、字符串字面量、操作符等等。当预处理器遇到一个宏定义的时候,会做一个 「标识符」代表「替换列表」的记录,然后再后面的文件内容中,不管标识符在哪里出现,预处理器都会用替换列表代替它。

不可以在宏定义中放置任何额外的符号,下面两个都是错误的。

1
2
#define N = 100
#define M 200;

13.3.2 带参数的宏

带参数的宏「C标准中称为 函数式宏」的定义格式:

1
#denfine 标识符(x1,x2,x3,...,xn)  替换列表

标识符和左括号之间必须没有空格。

1
#define MAX(x,y) ((x)>(y)?(x):(y))
  • 宏参数没有类型检查;当一个函数被调用时,编译器会检查每一个参数来确认他们是否是正确的类型,如果不是,会把参数转换成正确的类型,或者由编译器产生一条出错消息。而预处理器不会检查宏参数的类型,也不会进行类型转换。

  • 无法用指针指向一个宏。宏在预处理过程中会被删除。

  • 宏可能会不止一次得计算它的参数,副作用

    1
    2
    3
    4
    //预处理之前
    n = MAX(i++,j);
    //预处理之后
    n = ((i++)>(j)?(i++):(j);
  • 带参数的宏可以用于模拟函数调用简化重复书写的代码

    1
    #denfine PRINT_INT(n)   printf("%d\n",n)

13.3.3 #运算符

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#define PRINT_N(n) printf(#n " = %d\n",n)

int main(){
int i = 666;
PRINT_N(i);
return 0;
}


//输出 i= 666
  • 将宏的一个参数转换为字符字面量,仅仅允许出现在带参数的宏的替换列表中
    1
    2
    3
    4
    5

    - n之前的```#``` 运算符通知预处理器根据 PRINT_INT 的参数创建一个字符字面量,因此

    ```c
    PRINT_INT(i) => printf("i" " = %d\n",i);

    又因为c当中相邻的字符串字面量会被合并,因此:

    1
    printf("i = %d\n", i);

13.3.4 ##运算符

将两个几号(如标记符)粘合在一起。

1
2
3
4
5
6
7
8
9
10
11
12
#define GENERIC_MAX(type) 		\
type type##_max(type x, type y) \
{ \
return x>y?x:y; \
}
//这里相当于申明了函数
GENERIC_MAX(float);

int main(){

return 0;
}

\ 作用: 当定义的宏不能用一行表达完整时,\ 表示下一行继续此宏的定义。宏的最后一行不用加

在预处理之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1 "macro.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 361 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "macro.c" 2
//stdio.h 文件内容,此处省略



float float_max(float x, float y) { return x>y?x:y; };


int main(){
int i = 666;

float max = float_max(3.2,4.8);
printf("max is %f\n", max);

return 0;
}
//输出: max is 4.800000

与处理之后的代码中可以看到,声明的函数的名字已经改变了。已经粘合成 float_max , 并且宏中其他的 type 除也都替换成 float 了。

14.3.5 宏的通用属性

  • 宏的替换列表可以包含对其他宏的调用「我理解为宏嵌套」;
  • 宏定义的作用范围通常到出现这个宏的文件末尾;
  • 宏不可以被定义两边,除非心就定义是一样的;

14.3.6 宏定义中的圆括号

两个准则:

  1. 如果宏有参数,每个参数每次在替换列表中出现时,都需要放在圆括号中。
  2. 如果宏的替换列表中有运算符,那么始终要将替换列表放在括号中。

14.3.7 创建较长的宏

假如一个宏需要包含一系列的语句,而不及您是一系列的表达式,这时候就不能用逗号,因为逗号运算符只能连接表达式,不能连接语句,解决的方法是将语句放在do循环中,并设置循环条件。

1
2
3
4
5
#define ECHO(s)      \
do{ \
gets(s); \
puts(s); \
}while(0) //注意此处没有分号,但是在使用时要加分号

14.4 条件编译

条件编译指的是根据预处理器所执行的测试结果来包含或者排除程序片段。

14.4.1 #if #endif 指令

1
2
3
4
#if 常量表达式
..
..
#endif

如果常量表达式为1「真」,那么#if#endif中间的行将被执行,也就是保留给编译器,否则会在预处理过程中被删除。

14.4.2 defined 运算符

和 # ## 一样都是运算符,defined 运算符专用于预处理器。

当defined应用于标识符时,如果标识符是一个定义过的宏,则返回1,否则返回0;

1
2
3
#if defined DEBUG
...
#endif

仅当 DEBUG 被定义成宏时,#if #endif 中间的代码才会被保留在程序中。

14.4.3 #ifdef #ifndef 指令

用于测试一个标识符是否已经被定义成宏

1
2
#ifdef 标识符
#ii defined 标识符

两者是等价的

14.4.4 #elif #else 指令

与 #if #ifdef #ifndef 嵌套使用。

14.4.5 使用条件编译

  • 编写在多台机器或者多种操作系统之间可移植的程序
  • 编写可以用不同的编译器编译的程序。
  • 为宏提供默认定义
  • 临时屏蔽包含注释的代码
共字
0%
.gt-container a{border-bottom: none;}