预处理指令:
程序员所编写的代码并不能被编译器真正编译,需要一段程序把代码翻译一下
翻译的程序叫预处理器,翻译的过程叫预处理,被翻译的语句叫预处理指令,以#开头的语句都是预处理指令
查看预处理结果:
gcc -E xxx.c 把预处理的结果显示到终端
gcc -E xxx.c -o xxx.i 把预处理的结果存储到.i预处理文件中
预处理指令的分类:
#include 导入头文件
#incllude <> 从系统指定的路径下查找并导入头文件
#include"" 先从当前路径下查找头文件,如果找不到再从系统指定路径下查找并导入
编译参数 -I /path 也可以指定头文件查找路径
系统通过设置环境变量来指定头文件的系统查找路径
#define 定义宏
宏常量:
#define MAX 50
优点:提高了可读性和可扩展性,方便批量修改,安全性更高,还可以与case配合使用
注意:宏变量建议全部大写,末尾不要加分号
[函数名、局部变量小写加下划线,全局变量首字母大写、指针变量+p、数组arr、字符串str]
注意 typedef int num; 用法和含义上的区别
预定义好的宏常量:
__func__ 获取函数名%s
__FILE__ 获取宏所在文件名%s
__LINE__ 获取宏所在行数%d
__DATE__ 获取当前日期(年月日)%s
__TIME__ 获取当前时间(时分秒)%s
宏函数:
#define SUM(a,b) a+b
宏函数其实就是带参数的宏
宏函数不是真正的函数,不检查参数类型,没有传参和返回值
·如何实现宏函数:
1、先把代码中使用到宏函数的地方替换为宏函数后面的代码
2、再把宏函数代码中使用的参数替换为调用者提供的参数
宏的二义性:
由于宏的位置不同、参数不同导致宏有不同的功能,叫宏的二义性
如何避免宏的二义性:
宏函数整体加小括号,每个参数都加小括号保护
在使用宏函数的时候不要提供带自变运算符的变量作为参数
容易出选择题,提供宏函数问结果
运算符:
# 把宏函数的参数变成字符串
## 合并两个参数变成一个标识符
注意:宏函数可以用大括号保护代码,定义宏常量、宏函数时不能换行的,但是可以在一行的末尾使用续行符\换行
常考的笔试面试题:
1、#define和typedef有什么区别?
#define 是宏替换
typedef 是类型重定义
如果是普通类型时,从功能上没有任何区别
#define INT int
typedef int INT;
INT num;
·指针类型有区别时:
#define INT int*
INTP p1, p2, p3; //p1是指针 p2p3都是 int
typedef int* INTP;
INTP p1,p2,p3; //p1p2p3都是指针
2、sizeof和strlen有什么区别?
sizeof是一个计算变量、类型的字节数的运算符
strlen是一个计算字符串长度的函数
3、宏函数和普通函数有什么区别?
是什么?
宏函数:带参数的宏替换,不是真正的函数,只是用起来像函数
函数:是一段具有某项功能的代码的集合,会被编译成二进制指令存储在代码段中,
函数名就是该段代码的首地址,有独立的栈内存、命名空间
有什么不同:
函数:返回值 参数类型检查 安全 传参 速度慢 跳转
宏函数:运算结果 通用 危险 替换 速度快 冗余
条件编译
根据条件决定让哪些代码参与编译,哪些代码不参与
版本控制
#if
#elif
#else
#endif
注意:还可以使用#if 0 #endif 注释整段代码
头文件卫士:防止头文件重复包含
#ifndef 宏名(头文件名大写)(HEAD H)
#define 宏名
#endif//宏名
判断、调试
#if 宏名
#else
#endif
注意:可以在gcc编译时通过参数 -D宏名,为此次编译的结果添加宏
调试代码:
#ifdef DEBUG
#define debug(...) printf(__VA_ARGS__)
#else
#define debug(...)
#endif
提示错误信息:
#define error(...) printf("%s %s [%d] %s:%m %s %s\n",
__FILE__,__func__,__LINE__,__VA_ARGS__,__DATE__,__TIME__)
4、条件编译
根据条件决定哪些代码参与最终的编译
#if/#elif/#else/#endif/#ifdef/#ifndef
编译器控制:
#if __cpluseplus
printf("C++编译器\n");
#else
printf("C编译器\n");
#endif
操作系统位数:
#if __x86_64__
printf("64位操作系统\n");
#elif __i386__
printf("32位操作系统\n");
#endif
5、常考笔试面试
定义一个199年有多少秒的宏,忽略闰平年
#define SEC (3600*24*364*199u)
在类型重定义时,#define和typedef区别?
在定义常量时,#define和const有什么区别?
宏函数和普通函数的区别?
头文件中应该有什么?
问题:一个头文件可能被任何的源文件包含,意味着头文件内容可能出现在
多个目标文件中,链接合并时就可能会冲突
·重点:头文件中不能有定义语句(变量和函数),只能编写声明语句
全局变量声明
函数声明
宏常量
宏函数
typedef 类型
结构、联合、枚举的类型声明
头文件的编写要求:
1.为每个.c文件写一份.h文件,因为.h文件是对.c文件的说明
2.如果需要使用到某个.c文件中的变量、函数、宏时,只需要把他的头文件导入即可
3.每个.c文件都要导入自己的.h,目的是让定义和声明一致
头文件的相互包含:
假如a.h中包含了b.h的内容,b.h又包含了a.h,这种情况编译时就会出现
把a.h和b.h中要相互使用的东西都提取出来,再额外放入编写的c.h(可以没有c.c)
注意:unknown type name "未知类型名",如果已经确认过该类型、并正确导入,但是依然报为止类型名,一般是头文件互相包含
可以用a.c包含 b.h , b.c 包含 a.h
复制头文件卫士时粗心大意也可能会导致未知类型名
Makefile
Makefile 是由一系列的编译指令组成的可执行文本文件,也叫做编译脚本
在终端输入Makefile命令就可以自动执行Makefile脚本中的翻译指令,它可以根据脚本中的
编译指令的设置来编译整个工程,它还能决定编译先后顺序、哪些需要编译、哪些不需要编译,可以提高编译效率。
·编译规则:
1. 如果此项目没有编译过,则会编译全部的.c文件,并链接成可执行文件
2. 如果这个工程的某几个.c文件被修改,那么我们只编译被修改的 c 文件,并重新链接成可执行文件
3. 如果这个工程的.h文件被改变了,那么依赖它的所有.c文件都要重新编译,并重新链接成可执行文件