预处理
C语言的编译步骤
- 预处理
- 编译
- 汇编
- 链接
C语言的预处理
预处理就是在源文件编译之前,所进行的一部分预备操作,这部分操作是由预处理程序自动完成;当源文件在编译时,编译器会自动调用预处理程序来完成预处理操作执行的解析,预处理执行解析完成才能进入下一步的编译过程。
c
gcc 源文件 -E -o 程序名.[后缀]
预处理功能
宏定义
-
不带参数的宏定义
-
语法
c#define 宏名 常量数据
-
宏定义的预处理机制:此时的预处理只做数据替换,不做类型检查
-
注意:定义的宏不会占用内存空间,还未进入编译环节->运行。我们在编译前已经将宏名替换成了常量数据
-
宏展开:在预编译时将宏名替换成字符串的过程称为"宏展开"。这里的常量数据是一个不带双引号的字符串
案例
c#include <stdio.h> #define PI 3.1415926 int main() { float l,s,r,v; printf("请输入圆的半径:\n"); scanf("%f",&r); //计算周长 l = 2 * PI * r; //计算面积 s = PI * r * r; printf("l=%10.4f\ns=%10.4f",l,s); return 0; }
-
-
带参数的宏定义
-
语法:
c#define 宏名(参数列表) 参数表达式
-
面试题
c#define multi(a,b) a * b
- 实现
c# include <stdio.h> #define multi(a,b) a * b int main() { int result = multi(7+2,3); printf("%d\n",result);//13 return 0; }
-
-
宏定义的作用域
#define
命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。#define
写在文件开头,函数之前,作为文件一部分,在此文件范围内有效。- 可以有
#undef
命令终止宏定义
案例
c# 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范围 void fun1() { float r = 4; float s = PI * r * r; int day = DAY; } int main() { fun(); fun1(); return 0; }
-
在宏定义中引用已定义的宏名
- 案例
c#include <stdio.h> #define R 3.0 #define PI 3.14 #define L 2*PI*R #dedine S PI*R*R int main() { printf("%f,%f",L,S); return 0; }
文件包含
概念
所谓的文件包含处理是指一个源文件可以将另一个源文件的全部包含进来。这个适用于多文件开发。通常,一个常规的C语言程序会包含多个源码文件(*.c),当某些公共资源需要再各个源码文件中使用,为了避免多次编写相同的代码,我们会进行代码抽取(*.c
),并提供公共的访问文件(*.h
),然后再各个源码文件中直接包含即可。
*.h
中的函数申明必须要在*.c
中有对应的函数定义(函数的实现),否则没有意义。
头文件(.h)的内容
- 头文件中所存放的内容,就是各个源码文件的彼此可见的公共资源。
- 全局变量的声明
- 普通函数的声明
- 静态函数的定义
- 宏定义
- 结构体、联合体的定义
- 枚举常量列表的定义
- 其他头文件
- 实例
c
//head.h
extern int global;//全局变量
extern void f1();//普通函数的声明
static void f2();//静态函数定义
{
...;
}
#define max(a,b) ((a)>(b)?(a):(b))//宏定义
struct Mod //结构体定义
{
...;
};
union Ater //联合体定义
{
...;
};
#include <stdio.h>//其他头文件-系统库文件
#include "mylib.h"//其他头文件-自定义文件
-
特别说明:
- 全局变量、普通函数的定义一般出现在某个源文件(
*.c*.cpp
)中,其他源文件想要使用都需要进行声明,因此放在头文件更方便 - 静态函数、宏定义、结构体、联合体的定义都只能在其所在的文件可见因此多个源文件需要,放到头文件中定义更为方便、安全
- 全局变量、普通函数的定义一般出现在某个源文件(
-
文件包含预处理机制
此时的预处理,是将文件中的内容替换,文件包含指令
包含方式
-
#include <xxx.h>
系统会到标准文件目录(liunx下/user/include)查找包含文件
-
#include "xxx.h"
在工程路径下查找,若未找到仍然去标准库文件目录查找
案例
myhead.h
c
#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
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
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;
}
多文件编译命令
c
gcc app.c myhead.c -o app
条件编译
概念:根据设定的条件选择待选择编译的语句代码。
条件编译预处理机制:将满足条件的语句进行保留,不满足条件的语句进行删除,交给下一步编译
语法:
-
语法一
c#ifdef 标识 //判断标识符是否定义(定义为真,未定义为假) .. #else .. #endif
-
语法二
c#ifndef 标识 //判断标识符是否定义(未定义为真,定义为假) .. #else .. #endif
-
语法三
c#if 表达式 //判断表达式的结果(成立为真,不成立为假) .. #else .. #endif
案例
c#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的本质是复制粘贴,并且一个头文件可以嵌套包含其他头文件,因为很容易出现:头文件重复包含
语法:
c
#ifndef __XXXX_H //一般取头文件名称的大写
#define __XXXX_H
...
#endif