首页 C语言 预处理指令
文章
取消

C语言 预处理指令

文件包含

1
2
#include <stdio.h>
#include "userdef.h"

主要用来包含头文件,include 指令的处理很简单,就是把头文件插入到源文件,之后就不需要头文件了,因为头文件的内容都包含进来了。

一个 include 命令只能包含一个文件,可以有多个 include 指令,允许嵌套包含。

  • <>形式一般用于标准库头文件,""一般用于自定义头文件
  • <>的头文件搜索路径不包含当前路径,而""则包含当前路径

所以,可以都用""形式来包含头文件,包括系统的和自定义的,但最好按照规范来

我们来查看一下预处理的过程

1
2
3
4
5
6
#include <stdio.h>

int main(){
    printf("hello, world!\n");
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
$ gcc -E a.c -o a.i

$ tail a.i

extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 943 "/usr/include/stdio.h" 3 4

# 2 "a.c" 2

int main(){
    printf("hello, world!\n");
    return 0;
}

宏定义

允许用一个标识符来表示一个字符串

1
2
3
4
5
6
7
#include <stdio.h>
#define PI 3.14

int main(){
    printf("%.2f\n", PI);
    return 0;
}
1
2
3
4
$ gcc a.c

$ ./a.out
3.14

#define PI 3.14是一个宏定义,PI是一个宏名(宏名是标识符的一种,所以要遵循标识符的命名规则),3.14是宏的内容

在预处理时,所有遇到PI的地方,都会被替换为3.14,除了引号内的内容不会被替换,这个过程叫做宏展开

宏定义的作用域:从宏定义开始到源程序结束,除非用#undef来终止

1
2
3
4
5
6
7
8
#include <stdio.h>
#define PI 3.14

int main(){
#undef PI
    printf("%.2f\n", PI);
    return 0;
}

这样,在源文件第5行之后,这个宏定义就消失了


宏定义允许嵌套

1
2
#define PI 3.14
#define S PI*10*10

S展开后为3.14*10*10


宏名一般用大写,不过也可以用小写

可以用宏表示数据类型,书写方便(但建议使用 typedef)

1
2
#define UINT unsigned int
UINT a, b;

注意和typedef的区别:

宏定义只是简单的字符串代换,是在预处理完成的,而typedef是在编译时处理的,它不是作简单的代换,而是对类型说明符重新命名,被命名的标识符具有类型定义说明的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

#define PTR1 int *
typedef int * PTR2;

int main(){
    int a = 10, b = 20;
    PTR1 p1, p2;
    p1 = &a, p2 = &b;
    PTR2 p3, p4;
    p3 = &a, p4 = &b;
    printf("%d, %d, %d, %d\n", *p1, *p2, *p3, *p4);
    return 0;
}

这段代码编译会报错,因为,只有 p1、p3、p4 是指针变量,而 p2 只是一个 int 类型的变量

带参数的宏

定义:#define 宏名(形参列表) 字符串

使用:宏名(实参列表)

1
2
#define POW(x) x*x
int a = POW(10);

展开后会替换为int a = 10*10;

对于带参宏定义不仅要在参数两侧加括号,还应该在整个字符串外加括号,防止发生错误


如果一个宏一行写不下,可以用 \ 来续行,如:

1
2
3
4
5
6
7
8
#include <stdio.h>
#define STR \
    "https://www.zfl9.com/iptables.html?userid=d89sd9f2j398ds2&auth=true"

int main(){
    printf("%s\n", STR);
    return 0;
}

在宏定义中,有时还会用到###两个符号,它们能对宏参数进行操作

#用来将宏参数转换为字符串,也就是在宏参数前后添加双引号

1
2
3
#define STRING(str) #str
STRING(www.zfl9.com) //会替换为 "www.zfl9.com"
STRING("www.zfl9.com") //会替换为 "\"www.zfl9.com\""

##称为连接符,将宏参数和其他的串拼接起来

1
2
#define CAT(a, b) a##b
CAT(12, 34) //替换为1234

预定义的宏

  • __DATE__:当前日期,”MMM DD YYYY”格式的字符串常量
  • __TIME__:当前时间,”HH:mm:ss”格式的字符串常量
  • __FILE__:当前文件名,一个字符串常量
  • __LINE__:当前行号,一个十进制值常量
  • __STDC__:当编译器以 ANSI 标准编译时,被定义为 1
  • __cplusplus:当编写 C++ 程序时(C++ 环境),该标识符被定义

条件编译

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(){
    #if _WIN32
        printf("Windows\n");
    #elif __linux__
        printf("Linux\n");
    #endif
    return 0;
}
1
2
3
4
$ gcc a.c

$ ./a.out
Linux

意思是,如果宏_WIN32的值为真,则保留第 5 行的代码,如果宏__linux__的值为真,则保留第 7 行的代码

注意,判断条件为整型常量表达式,也就是说表达式中不能包含变量,而且结果必须是整数

#if

1
2
3
4
5
6
7
8
9
#if 整型常量表达式1
    //程序段1
#elif 整型常量表达式2
    //程序段2
#elif 整型常量表达式3
    //程序段3
#else
    //程序段4
#endif

#ifdef

判断某个宏是否已定义,定义了则为真

1
2
3
4
5
#ifdef  宏名
    //程序段1
#else
    //程序段2
#endif

#ifndef

#ifdef正好相反,未定义则为真

1
2
3
4
5
#ifndef 宏名
    //程序段1 
#else 
    //程序段2 
#endif

例子:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#define N1 10
#define N2 20

int main(){
    #if N1 == 10 || N2 == 10
        printf("N1: %d\n", N1);
    #else
        printf("error!\n");
    #endif
    return 0;
}
1
2
3
4
$ gcc a.c

$ ./a.out
N1: 10
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#define N1
#define N2

int main(){
    #if defined N1 && defined N2
        printf("True!\n");
    #else
        printf("False!\n");
    #endif
    return 0;
}
1
2
3
4
$ gcc a.c

$ ./a.out
True!

阻止编译

#error error_msg:强制终止编译。

如,我们的程序只支持 Windows,不兼容 Linux

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(){
    #ifndef _WIN32
        #error 抱歉,请使用 Windows 系统,该程序暂时不支持其他平台
    #endif
    return 0;
}
1
2
3
4
5
$ gcc a.c
a.c: In function ‘main’:
a.c:5:10: error: #error 抱歉,请使用 Windows 系统,该程序暂时不支持其他平台
         #error 抱歉,请使用 Windows 系统,该程序暂时不支持其他平台
          ^

小结

预处理指令是以#号开头行,#号必须是该行除了任何空白符外的第一个字符

#后是指令关键字,在关键字和#号之间允许存在空白字符,整行语句构成了一条预处理指令

该指令将在编译之前对源代码做某些转换,C/C++ 源程序经预处理后仍是合法的 C/C++ 源程序

本文由作者按照 CC BY 4.0 进行授权

C语言 指针

C语言 结构体