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>提供了三个全局变量:stdinstdoutstderr,只要包含了这个头文件就会自动创建它们。

二、标准库提供的操作文件接口

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是按照人的视角去设计的,每个元素多大,读多少元素;而不是像计算机一样,以字节为单位。

SUFE大二在读