C语言的文件IO操作
一、文件操作的模型
文件是保存在硬盘中的,而硬盘的操作依靠驱动。
1. C语言提供了标准库
作用:屏蔽了不同操作系统的syscall的不同,为应用程序提供统一的接口。
原因:由于硬盘的访问速度非常慢,所以CPU要耗费大量的时间去等待硬盘的执行,导致效率非常低。要解决这个问题,我们从减少访问硬盘的次数这方面着手。在内存中引入一段缓存区,先把硬盘里的一部分数据加载到里面,然后让CPU在执行指令时,先看缓存,没有就去看硬盘,这样效率就提高了。由此也可知,标准库提供的接口都是基于缓存的。
2. 缓存也有类型
a. 行缓存
遇到\n
才把缓存区里的内容刷新到驱动中。标准输入stdout
、标准输出都是属于这种。
// 该函数执行的结果是没有输出hello
void test01(){
printf("hello");
while(1);
}
// 该函数执行的结果是输出了hello
void test02(){
printf("hello\n");
while(1);
}
b. 不缓存
标准错误stderr
// 该函数会打印出err然后卡住
void test01(){
fprintf(stdout, "hello");
fprintf(stderr, "err");
while(1);
}
c. 全缓存
装满才刷新,文件对象一般默认是全缓存。
3. 更底层的printf
printf
是向标准输出打印信息。
int fprintf(FILE *stream, const char *format, ...);
FILE *
是C语言提供给程序员访问底层缓存的一个抽象对象,因为把FILE完全拷过来太大,没必要,所以就用的指针。
#include<stdio.h>
提供了三个全局变量:stdin
、stdout
、stderr
,只要包含了这个头文件就会自动创建它们。
二、标准库提供的操作文件接口
1. 向内核去申请访问这个文件的映射关系,同时开辟缓存
FILE *fopen(const char *path, const char *mode); // 这个返回的地址指向堆区,空间是被malloc申请来的
int fclose(FILE *stream); // 内部使用了free释放空间,所以fopen和fclose要配合使用
常用的文件打开模式:
r
: 只读打开文件,文件必须存在;不存在就报错w
: 只写打开文件,文件不存在就创建;存在就清空+
: 必须配合r、w进行组合。r+
表示读写打开文件,后面的不变;w+
同理b
: 以二进制方式打开。实际的意义是,一旦使用了这个标签,那么后面采用文本方式读写文件的接口就不允许使用了
文本文件和二进制文件的区别
文本文件: 以Byte为单位的文件中,使用ASCII码解码时,比如遇到10会把它当作'\n'
处理。
二进制文件:以Byte为单位的文件中,10只是一种数据,不具有换行的意义。
2. 常用IO接口
a. 按字节来读写
可以以二进制访问方式操作
读fgetc
,写 fputc
b. 按行来读写(文本方式)
读fgets
,这个函数是相当安全的函数。相对应的,gets
函数是危险函数。
// gets不会检查越界这种问题,可能会修改到系统重要的值
char *gets(char *str); // 从标准输入中接收用户传来的数据,到str指向的空间里。
// 对于一个字符串来说,必须要有0值(\0)作为结束标志。
// 所以对于字符数组来说,我们要预留一个位置来专门存放结束标志。
char *fgets(char *str, int size, FILE *stream);
char buf[16];
fgets(buf, sizeof(buf), stdin); // 从stream读取数据到str空间里
// 有2个结束标志:
// 1. 最多读取size - 1个字符,函数返回前,主动在最后加0
// 2. 在读到最大字符个数前,遇到\n后也不再读入
scanf("%d", &a);
表示从标准输入中读取字符串,把这个字符串按照有符号十进制的转码方法,转成有符号的十进制数字,赋给a
c. 按块来读写
读fread
,写fwrite
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
fread是按照人的视角去设计的,每个元素多大,读多少元素;而不是像计算机一样,以字节为单位。