C语言:预处理
预处理
C语言的编译步骤
- 预处理
- 编译
- 汇编
- 链接、
什么是预处理
预处理就是在源文件(如.c文件)编译之前,所进行的一部分预备操作,这部分操作是由预处理程序自动完成;当源文件在编译时,编译器会自动调用预处理程序来完成预处理执行的解析,预处理执行解析完成才能进入下一步的编译过程。
我们为了能够方便的看到这个编译细节,我们可以使用下面命令:
gcc 源文件 -E -o 程序名[.后缀]
预处理功能
宏定义
-
不带参数的宏定义
-
语法:
#define 宏名 常量数据
-
预处理:此时的预处理只做数据替换,不做类型检查
-
注意:我们定义的宏是不会占用内存空间,还没有进入到编译环节,更不要说运行了,在编译前已经将宏名替换成了常量数据
-
宏展开;在预编译时将宏名替换成字符串的过程称为“宏展开”。这里的常量数据其实就是一个不带双引号的字符串。
-
案例:
-
#include <stdio.h>
#define PI 3.1415926
int main(int argc, char *argv[])
{
float l, s, r, v;
printf("请输入圆的半径:\n");
scanf("%f", &r);
// 计算周长
l = 2.0 * PI * r;
// 计算面积
s = PI * r * r;
printf("l=%10.4f\ns=%10.4f\n", l, s);
return 0;
}
-
带参数的宏定义
-
语法:
#define 宏名(参数列表) 参数表达式
实现
#include <stdio.h> // 定义宏 #define multi(a, b) a *b int main(int argc, char *argv[]) { int result = multi(7 + 2, 3); printf("%d\n", result); // 13 return 0; }
-
-
宏定义的作用域
-
#define
命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。通常#define
命令写在文件开头,函数之前,作为文件一部分,在此文件范围内有效。 -
可以用
#undef
命令终止宏定义的作用域 -
案例:
#include <stdio.h> #define PI 3.14 #define DAY 20 void fun() { float r = 4; float s = PI * r * r; int day = DAY; } #undef PI // 终止了PI的范围 #define PI 3.1415926 void fun1() { float r = 4; float s = PI * r * r; int day = DAY; } int main(int argc, char *argv[]) { fun(); fun1(); return 0; }
-
-
在宏定义中引用已定义的宏名
-
案例:
#include <stdio.h> #define R 3.0 #define PI 3.14 #define L 2 * PI *R #define S PI *R *R int main(int argc, char *argv[]) { printf("L=%f\nS=%f\n", L, S); return 0; }
-
文件包含
概念
所谓“文件包含”处理是指一个源文件可以将另一个源文件的全部内容包含进来。这个适用于多文件开发。通常,一个常规的C语言程序会包含多个源码文件(*.c
),当某些公共资源需要在各个源码文件中使用时,为了避免多次编写相同的代码,我们一般会进行代码的抽取(*.c
),并提供公共的访问文件(*.h
),然后在各个源码文件中直接包含即可。
注意:*.h
中函数的声明必须在*.c
中有对应函数的定义(函数的实现),否则没有意义。
头文件(.h)的内容
-
头文件中所存放的内容,就是各个源码文件的彼此可见的公共资源,包括:
- 全局变量的声明
- 普通函数的声明
- 静态函数的声明
- 宏定义
- 结构体、联合体的定义
- 枚举常量列表的定义
- 其他头文件?
-
示例代码:
// head.h extern int global;// 全局变量的声明 extern void f1();// 普通函数的声明 static void f2() // 静态函数的定义 { ... } #define MAX(a,b) ((a)>(b)?(a):(b)) // 宏定义 struct node // 结构体的定义 { ... }; union attr // 联合体的定义 { ... }; #include <stdio.h> // 其他头文件 #include "mylib.h" // 其他头文件-自定义文件
-
特别说明:
- 全局变量、普通函数的定义一般出现某个源文件(
*.c,*.cpp
)中,其他的源文件想要使用都需要进行说明,因此一般放在头文件中更方便 - 静态函数、宏定义、结构体、联合体的定义都只能在其所在的文件可见(说白了,就是在预处理阶段直接从头文件拷贝)因此如果多个源文件都需要的话,放到头文件中定义是最方便,也是最安全的选择。
- 全局变量、普通函数的定义一般出现某个源文件(
-
文件包含预处理机制:
此时的预处理,是将文件中的内容替换,文件包含指令。
-
包含方式:
-
第1种:
#include <xxxx.h>
系统会到标准库文件目录(Liunxc下
/usr/include
) -
第2种:
#include "xxxx.h"
在当前工作路径下查找,如果未找到,仍然会到标准库文件目录查找。建议对于自定义库采用这种写法
-
-
案例:
myhead.h
#ifndef _MYHEAD_H #define _MYHEAD_H // 数组的累加和计算 extern int sum(const int *, int); // extern int sum(const int *p,int len); #endif //_MYHEAD_H
myhead.c
#include "myhead.h" /** * 数组的累加和计算 */ int sum(const int *p, int len) { int sum = 0; register int i = 0; for (; i < len; i++) { sum += *(p + i); } return sum; }
app.c
#include <stdio.h> #include "myhead.h" int main(int argc, char *argv[]) { int arr[5] = {12, 13, 14, 15, 16}; int res = sum(arr, 5); printf("数组累加和的结果是:%d\n", res); return 0; }
多文件编译命令
gcc app.c myhead.c -o app
条件编译
-
案例:
#include <stdio.h> #define LETTER 0 // 默认大写 int main(int argc, char *argv[]) { // 测试用的字母字符 char str[20] = "C Language"; char c; int i = 0; while ((c = str[i]) != '\0') { i++; #if LETTER if (c >= 'a' && c <= 'z') { c -= 32; } #else if (c >= 'A' && c <= 'Z') { c += 32; } #endif printf("%c", c); } printf("\n"); return 0; }
避免头文件重复包含的方法
由于头文件包含指令#include的本质是复制粘贴,并且一个头文件中可以嵌套包含其他头文件,因此很容易出现一种情况是:头文件被重复包含。
语法:
#ifndef __XXXX_H // 一般取头文件名称的大写
#define __XXXX_H
..
#endif
案例:
myhead.h
#ifndef _MYHEAD_H
#define _MYHEAD_H
// 数组的累加和计算
extern int sum(const int*,int);
// extern int sum(const int *p,int len);
#endif //_MYHEAD_H
#### 避免头文件重复包含的方法
由于头文件包含指令#include的本质是复制粘贴,并且一个头文件中可以嵌套包含其他头文件,因此很容易出现一种情况是:头文件被重复包含。
语法:
```c
#ifndef __XXXX_H // 一般取头文件名称的大写
#define __XXXX_H
..
#endif