一、预处理指令
程序员所编写的代码不能被真正的编译器所编译,需要先经过一段程序翻译一下
翻译的过程称为预处理,负责翻译的程序称为预处理器,被翻译的语句叫做预处理指令,以#开头的都是预处理指令
查看预处理结果:
gcc -E code.c 把预处理的结果显示到终端
gcc -E code.c -o code.i 把预处理的结果存储到.i预处理文件
二、预处理指令的分类
#include 头文件导入(拷贝)
通过设置环境变量来制定路径
#define 定义宏
宏常量:
#define MAX 50
优点:提高代码可扩展性、提高可读性、提高安全性、还可以与case配合
预定义好的宏常量:
__ func __:获取函数名
__ FILE __:获取文件名
__ LINE __:获取行号
__ DATE __:获取日期
__ TIME __:获取时间
注意:定义宏常量不要加 分号
一般宏名全部大写
宏函数:
是带参数的宏
不是真正意义上的函数,没有发生传参,没有返回值,不会去检查参数的类型
普通函数:一段具有某项功能的代码集合,会被编译成二进制指令存储在代码段中,函数名就是它的首地址,有独立的栈内存
宏函数:带参数的宏替换,不是真正的函数,用起来像函数,没有独立的栈内存
#define SUM(a,b) a+b
// 作用:1、先把代码中出现了宏函数的位置,替换成宏函数后面的语句
// 2、再把代码中使用的参数替换成调用者的参数
#define 与 typedef 的区别
#define INT int
typedef int INT
// 一个是代码替换,一个是类型重定义
如果是普通类型,它们在功能上无任何区别,但本质不同。
但
#define INTP int*
INTP p1,p2,p3; //p1是指针,p2,p3是int
typedef int* INTP
INTP p1,p2,p3; // p1,p2,p3都是指针
注意:宏的内容必须保证在同一行,如果要换行,要在每一行的末尾添加续航符 \
宏函数的二义性:
由于宏函数代码位置、附近的值、参数等各种原因的影响,会导致宏函数有不同的解释,这叫做宏的二义性
如何避免宏的二义性:
每个参数都加小括号,整体也加小括号,不要在宏函数的参数中使用自变运算符
三、条件编译
根据条件决定让代码是否参与最终的编译
版本控制:
#if
#elif
#else
#endif
#include <stdio.h>
#define VERSION 3
int main()
{
//#if 0
#if VERSION > 2
printf("\n");
#elif VERSION > 1
printf("\n");
#else
printf("\n");
#endif
//#endif
}
头文件卫士:
防止头文件被重复包含,头文件必加
#ifndef 宏名(头文件名大写_H) // 如果宏名不存在为真
#define 宏名(头文件名大写_H)
//
#endif//头文件名大写_H
判断、调试:
#ifdef 宏名// 如果宏名存在为真
#else
#endif
在编译时添加宏DEBUG:gcc 程序名.c -DDEBUG
#include <stdio.h>
// 打印调试信息
#ifdef DEBUG
#define debug(...) printf(__VA_AGRS__)
#else
#define debug(...)
#endif
// 打印错误信息
#define error(...) printf("%s\n %s:%d\n %s %m\n %s %s\n",__FILE__,__func__,\
__LINE__,__VA_ARGS__,__DATE__,__TIME__)
int main()
{
int num = 0;
int ret = scanf("%d",&num);
debug("ret=%d\n",num);
FILE* fp = fopen("a.out","r");
if(NULL == fp)
{
error("fopen");
return 0;
}
printf("Success\n");
return 0;
}
四、头文件中应该写什么
头文件可能会被任意源文件包含,意味着头文件中的内容可能会在多个目标文件中存在,要保证合并时不要冲突
重点:头文件只编写声明语句,不能有定义语句
全局变量声明
函数声明
宏常量
宏函数
typedef 类型重定义
结构、枚举、联合的类型设计声明
头文件的编写规则:
1、为每个 .c 文件写一份 .h 文件,.h 文件是对它对应的 .c 文件的说明
2、如果需要用到某个 .c 文件中的变量、函数、宏时,只需要把该文件的.h文件导入即可
3、.c 文件也要导入自己的 .h 文件,目的是为了让定义与声明保持一致
头文件的相互包含:
假如 a.h包含了 b.h 的内容,而 b.h 中又包含了 a.h 的内容,这时就会产生头文件的相互包含,无法编译通过
解决方案:把 a.h 中需要 b.h 的内容,和 b.h 中需要 a.h 的内容提取出来,额外再写另一个 c.h
五、Makefile:
Makefile 是由一系列的编译器指令组成的可执行文件,叫做编译脚本
在终端执行 make 命令就会自动执行 Makefile 脚本中的编译指令,它可以根据文件的修改时间和依赖关系来判断哪些文件需要编译,哪些不需要编译
需要一个名字叫做 Makefile 文件
Makefile的编译规则:
1、如果这个工程没有编译过,那么我们的所有 c 文件都要编译并链接
2、如果这个工程的某几个 c 文件被修改,那么我们只编译被修改的 c 文件,并重新链接目标程序
3、如果这个工程的有文件被更改了,那么引用了这几个头文件的 c 文件都会重新编译,并链接目标程序
一个最简单的Makefile脚本格式:
执行目标:依赖
编译指令
被依赖的目标1:依赖的文件
编译指令
被依赖的目标2:依赖的文件
编译指令
......