C语言预处理指令

预定义

  include指令是一种常用的预处理指令。通过include指令,可以将其他文件中的代码引入到当前文件中,从而实现代码的复用和模块化开发。
  include通常用于包含.h的头文件,尽管可以包含.c文件,但是这是错误的操作。
  include包含的文件会在预编译时展开,所以当一个.c文件被包含,将会出现多次展开,从而导致函数重复定义,而.h文件如果被多次包含,也会出现多次声明的情况。

  用尖角号包含头文件,在系统指定的路径下找头文件

1
#include<stdio.h>

  用双引号包含头文件,现在当前目录下找头文件,如果当前目录下不存在,再到系统指定的路径下找头文件
1
#include "demo.h"

宏定义

  在预编译时预处理器会将替换为对应的值,其格式是如下,其中宏名是一个标识符,值可以是一个常量、关键字、一段表达式或者函数,当某个值被宏名定义后,在代码中就可以直接用宏名来取代值

1
#define 宏名 值

宏定义中的常量替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>

// 将__NUMBER_1__替换为12
#define __NUMBER_1__ 12

int main() {
// __NUMBER_1__的值被替换为12
int num1 = __NUMBER_1__;
// 将会输出12
printf("num1 = %d\n", num1);

// 终止__NUMBER_1__的替换
#undef __NUMBER_1__

// 重新将__NUMBER_1__替换为10
#define __NUMBER_1__ 10
// 将__STRING_1__替换为str1
#define __STRING_1__ "str1"
// __NUMBER_1__的值被替换为10
num1 = __NUMBER_1__;
// 将会输出10
printf("num1 = %d\n", num1);

// __STRING_1__的值被替换为str1
char* str1 = __STRING_1__;
// 将会输出20
printf("str1 = %s\n", str1);

return 0;
}

// 终止__NUMBER_1__和__STRING_1__的替换
#undef __NUMBER_1__
#undef __STRING_1__

宏定义中的关键字替换

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

// 将st替换为static关键字
#define st static

// 直接用st修饰供外部访问的变量
st int num;

int main() {
num = 10;
printf("num = %d\n", num);

return 0;
}

// 终止st的替换
#undef st

宏定义中的表达式替换

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

// 将over替换为return 0关键字
#define over return 0;

int main() {
printf("hello world");

// 等同于 return 0;
over
}

// 终止over的替换
#undef over

宏定义中的函数替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

#define ex extern
// 将function();方法调用替换为fun
#define fun function()

ex char* function();

int main() {
printf("%s\n",fun);

return 0;
}

char* function() {
return "hello world";
}

// 终止fun的替换
#undef fun

带参数的宏定义替换

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

// 将S(a,b)类型替换成a+b
#define S(a,b) a+b;

int main() {
int sum = S(1, 2);
printf("sum = %d", sum);

return 0;
}

// 终止fun的替换
#undef S(a,b)

条件编译

  通过条件编译预处理指令,可以根据条件判断是否编译某段代码

#if…#elif…#else…#endif

  首先判断#if后的条件,条件不满足则判断#elif后的条件,均不满足则执行#else,并以#endif结束条件编译
  下面代码中defined(宏名)用于判断某个宏名是否被定义过,被定义过返回1,否则返回0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

// 对DEBUG宏定义,不用添加值
#define __DEBUG__

char* model;

int main() {

#if defined(__DEBUG__) // 当DEBUG被宏定义时
model = "调试模式";
#elif defined(__RELEASE__) // 当RELEASE被宏定义时
model = "发布模式";
#else // 没有进行DEBUG或RELEASE宏定义
model = "错误";
#endif // 结束条件编译
// 将会输出“调试模式”,因为DEBUG被宏定义
printf("%s\n", model);

return 0;
}

#ifdef…#else…#endif

  首先判断#ifdef后的宏名是否被定义,未被定义则执行#else,并以#endif结束条件编译

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

// 对RELEASE宏定义,不用添加值
#define __RELEASE__

char* model;

int main() {

#ifdef __RELEASE__ // 判断RELEASE是否被宏定义
model = "发布模式";
#else // 没有被宏定义
model = "调试模式";
#endif // 结束条件编译
// 将会输出“发布模式”,因为RELEASE被定义
printf("%s\n", model);

return 0;
}

#ifndef…#else…#endif

  与#ifdef相对应,首先判断#ifndef后的宏名是否未被定义,被定义则执行#else,并以#endif结束条件编译

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

char* model;

int main() {

#ifndef __RELEASE__ // 判断__RELEASE__是否未被宏定义
model = "发布模式";
#else // 被宏定义
model = "调试模式";
#endif // 结束条件编译
// 将会输出“发布模式”,因为RELEASE未被定义
printf("%s\n", model);

return 0;
}

防止复引用

  当一个.h的头文件被#include多次包含时,头文件中的声明语句将会被多次声明,使用选择编译的方式可以防止这种情况,使头文件在第二次被展开时不编译声明语句

  如下代码,当.h的头文件第一次被引用时,宏名PROJECT_NAME_DEMO_H是未定义状态,代码体将会被展开编译,当头文件被第二次引用时,PROJECT_NAME_DEMO_H已经被定义,代码体不被展开编译

1
2
3
4
5
6
#ifndef PROJECT_NAME_DEMO_H // 判断宏名PROJECT_NAME_DEMO_H是否没有被定义过
#define PROJECT_NAME_DEMO_H // 定义宏名,无需定义值

...//代码体

#endif //PROJECT_NAME_DEMO_H// 结束