复习:
1、输出缓冲区
程序输出的数据并没有立即写入"文件",而是先存储到输出缓冲区中,当满足一定条件时才写入文件中:
1、遇到'\n'
2、遇到输入语句
3、缓冲区满4k
4、程序结束
5、手动刷行 fflush(stdout) windows没有
2、输入缓冲区
在终端输入的数据,按下回车后,会从终端存储进输入缓冲区中,再执行输入函数时,会从输入缓冲区中读取相应的数据
1、当想要从缓冲区中读取整型、浮点型数据,而缓冲区中的数据是字符、字母时,此次读取会失败,而且不会从缓冲区中拿走该数据,导致接下来所有数据的读取都出问题
解决方法:根据scanf的返回值判断是否全部成功接收数据,否则就清空输入缓冲区再重新输入
stdin->_IO_read_ptr = stdin->_IO_read_end;
2、使用fgets输入时,超过size-1个字符就会残留在输入缓冲区中,影响下一次的输入
解决方法:判断缓冲区中是否会有'\n',如果有
scanf("%*[^\n]");
scanf("%*c");
或者
stdin->_IO_read_ptr = stdin->_IO_read_end;
3、先输入其他类型数据,再输入字符、字符串数据,那么其他类型数据输入时最后按下的'\n'或者空格就会被字符、字符串接收,影响正常的输入
字符:
scanf(" %c");
字符串:
scanf("%d",&num);
scanf("%*c");
gets(str);
3、常考的字符串处理函数
stelen\strcpy\strcat\strcmp
strlen和sizeof区别
memcpy\memcat\memcmp\memmove
sprintf 把各种类型数据拼接成字符串
sscanf 从字符串解析各种类型数据到变量中
一、预处理指令
程序员所编写的代码并不是真正的标准c代码,需要一段程序翻译成标准c代码,才能被编译器编译
翻译的过程叫做预处理,负责翻译的程序叫做预处理器,被翻译的语句叫做预处理指令,以'#'开头的都是预处理指令
gcc -E code.c 直接显示预处理后的结果
gcc -E code.c -o code.i 生成预处理文件
二、预处理指令的分类
'#include' 导入头文件/头文件包含
'#include <>' 从系统指定路径查找并导入头文件
'#include ""' 先从当前工作路径查找,找不到,再从系统指定路径查找并导入头文件
gcc code.c -I path 编译时指定头文件的加载路径为path,最先从该路径查找
可以通过设置环境变量来修改、增加系统指定头文件加载路径
修改~/.bashrc 终端配置文件
'#define' 定义宏
宏常量:'#define' 宏名 常量值
'#define MAX 100'
其实本质上就是,在代码中出现了宏名的地方,在预处理时替换为对应的常量值
优点:提高可拓展性、可读性、安全性,还可以用在case后面
注意:一般宏名全部大写,末尾不加分号
局部变量、函数名全部小写,全局变量首字母大写,数组arr,指针p、pp,字符串str
预定义好的宏:
__func__ 获取函数名
__FILE__ 获取文件名
__DATE__ 获取当前日期(月、日、年)
__TIME__ 获取当前时间
__LINT__ 获取当前行数 %d
宏函数:其实就是带参数的宏
'#define' 宏名(参数名) 替换的代码
宏函数不是真正的函数,不检查参数类型、没有传参、只有表达式的计算结果没有返回值
'#define SUM(a,b) a+b'
SUM(num1,num2)
替换过程:
1、先把代码中使用到宏函数的地方替换为宏函数后面的代码 a+b
2、再把宏函数代码中使用到的参数替换为调用者提供的数据 num1+num2
注意:宏函数后面的替换代码不能直接换行,需要在每一行末尾通过续航符 \ 换行
如果有多行代码,可以使用大括号保护代码
宏的二义性:
由于宏代码所处位置、参数位置、优先级问题,导致同一个宏函数有不同的可解释结果
如何避免宏的二义性:
1、给每个参数加小括号,给整个式子加小括号
2、在使用宏函数时,不要提供自变运算符的参数
注意:容易出选择题
练习1:实现一个交换两个变量的值的宏函数,数据类型通用,能写多少个,分析优劣
'#define SWAP(a,b) {typeof(a) (t)=(a);(a)=(b);(b)=(t);}'
/day08/6.c
常考笔试面试题:
'#define'与'typedef'的区别
'#define INT int'
'typedef int INT'
INT num
如果是普通类型,它们功能上没有任何区别
'#define INTP int*'
'typedef int* INTPP'
INTP p1,p2,p3 //p1是int* p2,p3是int
int* p1,p2,p3 //p1,p2,p3都是int*
宏函数和普通函数的区别?
它们是什么?
宏函数:带参数的宏替换,只是代码替换,只是使用时像函数而已,不是正在的函数
函数:是一段具有某项功能的代码集合,会被翻译成二进制指令存储代码段,函数名就是它的首地址,有独立的栈内存、命名空间
有什么不一样:
宏函数:运算结果 通用 危险 替换 速度快 代码冗余
函数: 返回值 类型检查 安全 内存申请、释放 速度慢 函数跳转
注意:调用频繁、内容简单的功能适合写成宏函数
条件编译:
根据条件决定哪些代码是否参与最终的编译
版本控制:
'#if 条件'
'#elif 条件'
'#else'
'#endif'
头文件卫士
防止头文件被 重复 包含
'#ifndef' 宏名
'define' 宏名
'endif' //宏名
宏名:头文件名全部大写,小数点用下划线代替
注意:头文件中必加上头文件卫士
调试、判断:
'#ifdef 宏名'
'else'
'endif'
gcc code.c -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__)'
三、头文件
头文件中应该写什么?
问题:头文件可能会被任意源文件包含,意味着头文件中的内容会在多个目标文件中同时存在,因此要满足合并内容不能有冲突
重点:头文件中只能编写声明语句,绝对不能有定义语句
函数声明
全局变量声明
宏常量
宏函数
typedef 类型重定义
结构、联合、枚举的类型说明
头文件的编写原则
1、为每个.c文件编写一份.h文件,.h文件是对.c文件的说明
2、如果需要用到某个.c文件中的全局变量、函数、宏等内容时,只需要把它的头文件导入即可
3、.c文件也要导入它自己的.h文件,目的是为声明与定义一致
头文件的相互包含:
假设a.h包含了b.h,b.h又包含了a.h,此时编译会出错
解决方案:把a.h需要导入b.h的内容和b.h需要导入a.h的内容提取出来到另一个头文件c.h、
错误提示:未知类型名"XXXX",一般就是头文件相互包含