C语言文件操作
文件
- 文件的概念实际是是非常广的,在程序员看来,万物皆文件,显示器是文件,键盘鼠标是文件,网络也是文件,网卡也是文件,等等···都是文件。我们现在就暂且就先把文件理解为我们接触最多的磁盘上的文件。在程序设计中,一般谈的文件有两种:程序文件和数据文件。
程序文件:
包含源程序文件(.c),目标文件(.obj),可执行文件(.exe)。
数据文件:
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者是输出到文件中。(例:就是我们曾经写过的C语言的学生信息管理系统)
文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
- 文件名包含3部分:文件路径+文件名主干+文件后缀。例:
c:\file\test.txt
- 为了方便起见,我们把文件标识叫做文件名。
文件类型
根据数组的组织形式,数据文件被称为 文本文件 或者 二进制文件 。
- 二进制文件:数据在内存中以二进制的形式存储,如果不加任何的修改,直接写入外存,写入的这个文件就是二进制文件。
- 文本文件:但是我们根本不认识二进制文件,我们需要看的是文字,那么就要求在外存中以ASCII码的形式存储,则需要在存储前转换。这种以ASCII字符的形式存储的文件就是文本文件。
文件缓冲区
ANSI C标准采用“缓冲文件系统”处理数据文件,所谓缓冲文件管理系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存的文件缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定。
什么是文件缓冲区
- 本质是内存里面的一段内存空间(C帮我们维护)。
为什么存在文件缓冲区
先来举一个例子,我们为了给远在他方的朋友送一个礼物(假设很远),我们会自己乘坐交通工具过去吗?显然我们并不会,这样做既费时间,又费精力。我们最常见的作法是给快递公司,然后让快递公司去帮我们把物品送到朋友的手中。然后快递公司的做法是,堆积了一定的物品之后,才会发车送快递,并不会,你把物品给他,他就直接发出去帮你送快递。结合生活经验,再来理解缓冲区就变的简单多了。
在计算机的视角,缓冲区的存在,是为了提高IO效率。
这里的我们就是CPU,礼物就是数据,朋友的地址就是外设,快递公司就是文件缓冲区。对于CPU来说,外设的距离实在是太远了,频繁的进行IO操作,会严重降低计算机的效率的。所以,现在开了一家快递公司,就是文件缓冲区,攒够了一定的数据之后,就进行一次IO操作。提高了计算机的效率。
缓冲区的刷新方式
- 无缓冲:即没有缓冲区,直接进行IO操作。
- 行缓冲:遇到
'\n'
刷新缓冲区,常见于往显示器写入数据,因为我们的习惯,行缓冲较为舒适。 - 全缓冲:缓冲区满了,才刷新缓冲区(效率最高),常见于正常文件的情况。
缓冲区的刷新时机
- 如果是全缓冲,缓冲区满了才进行刷新。
- 如果是行缓冲,遇到
'\n'
才进行刷新。 - 程序退出的时候,会进行缓冲区的刷新。
- 强制刷新,
fflush()
或者fclose()
都会强制刷新缓冲区。
文件指针
我们刚刚提到了ANSI C标准采用“缓冲文件系统”处理数据文件,在缓冲文件系统中,最为关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(文件的名字,文件的状态及文件的当前的位置等),这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE
该结构体如下(了解即可):
struct _iobuf{
char* _ptr;
int _cnt;
char* _base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char* _tmpfname;
};
typedef struct _iobuf FILE;
- 不同的C编译器的
FILE
类型包含的内容不完全相同,但是基本差不多,大同小异了。 - 每打开一个文件,系统会根据文件的情况自动创建一个
FILE
结构的变量,并填充其中的信息,使用者不必关心细节。 - 定义一个指向FILE类型的数据的指针变量,可以使指针指向某个文件的文件信息区(结构体变量),通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
文件的打开与关闭
ANSI C规定使用
fopen
函数来打开文件,fclose
来关闭文件;
打开文件的方式:
文件的使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
"r" (只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
"w" (只写) | 为了输出数据,打开一个文本文件 | 建立一个新文件 |
"a" (追加) | 向文本文件末尾添加数据 | 出错 |
"rb" (只读) | 为了输入数据,打开一个二进制文件 | 出错 |
"wb" (只写) | 为了输出数据,打开一个二进制文件 | 建立一个新文件 |
"ab" (追加) | 向一个二进制文件末尾添加数据 | 出错 |
"r+" (读写) | 为了读和写,打开一个文本文件 | 出错 |
"w+" (读写) | 为了读和写,建一个新的文件 | 建一个新的文件 |
"a+" (读写) | 打开一个文件,在文件末尾进行读写 | 建立一个新的文件 |
"rb+" (读写) | 为了读和写,打开一个二进制文件 | 出错 |
"wb+" (读写) | 为了读和写,建一个新的二进制文件 | 建立一个新的文件 |
"ab+" (读写) | 打开一个二进制文件,在文件末尾进行读和写 | 建立一个新的文件 |
C语言默认会打开的文件(设备)
文件 | 文件指针 |
---|---|
键盘 | stdin |
显示器 | stdout |
显示器 | stderr |
文件的顺序读写
何为顺序读写?简单的我们可以理解为从前往后有顺序的读或写。
功能 | 函数名 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输入流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输入流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输入流 |
二进制输入函数 | fread | 文件 |
二进制输出函数 | fwrite | 文件 |
文件的随机读写
有了顺序读写,我们还有随机读写(随机的概念可以参考RAM的概念)的需要,接下来介绍几个随机读写函数。
ftell
返回文件指针相对于文件起始位置的偏移量
long int ftell(FILE* stream);
- 参数为:要操作的文件指针。
fseek
根据文件指针的位置和偏移量来定位文件指针。
int fseek(FILE* stream, long int offset, int origin);
- 参数分别为:要操作的文件指针,相对于给定位置的偏移量,给定一个开始位置。
origin
(开始位置)的可能取值:文件头SEEK_SET
,当前位置SEEK_CUR
,文件尾SEEK_END
rewind
让文件指针的位置回到文件的起始位置。
void rewind(FILE* stream);
- 参数为:要操作的文件指针。
文件的结束判定
牢记:在文件读取过程中,不能使用
feof
函数的返回值直接来判断文件是否结束。而是用于当文件读取结束的时候,判断是读取失败还是遇到文件末尾而结束的。
- 文本文件读取是否结束,判断返回值是否为
EOF
(fgetc
),或者NULL
(fgets
)
例如:fgetc
判断是否为EOF
;fgets
判断返回值是否为NULL
。 - 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:fread
判断返回值是否小于实际要读的个数。