目录
1. 文件
1.1 为什么使用文件?
1.2 什么是文件?
1.2.1 程序文件
1.2.2 数据文件
1.3 文件名
1.4 二进制文件和文本文件
2. 文件的打开和关闭
2.1 流和标准流
2.1.1 流
2.1.2 标准流
2.2 文件指针
2.3 文件的打开和关闭
3. 文件的顺序读写
3.1 顺序读写函数介绍
3.1.1 fgetc 与 fputc
3.1.2 fgets 与 fputs
3.1.3 fprintf与 fscanf
3.1.4 fread 与 fwrite
3.1.5 sscanf 与 sprintf
4. 文件的随机读写
4.1 fseek函数
4.2 ftell函数
4.3 rewind函数
5 文件读取结束的判定
5.1 feof函数与ferror函数
6. ⽂件缓冲区
1. 文件
1.1 为什么使用文件?
如果没有文件 ,我们写的程序的数据是存储在电脑的内存中 ,如果程序退出 ,内存回收 ,数据就丢失了 ,等再次运行程序 ,是看不到上次程序的数据的 ,如果要将数据进行持久化的保存 ,我们可以使用文件。
1.2 什么是文件?
磁盘上的文件是文件。但是在程序设计中 ,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。
1.2.1 程序文件
程序文件包括源程序文件(后缀为.c) , 目标文件(windows环境后缀为.obj) ,可执行程序(windows 环境后缀为.exe)。
1.2.2 数据文件
文件的内容不一定是程序 ,而是程序运行时读写的数据 ,比如程序运行需要从中读取数据的文件 ,或者输出内容的文件。
本章讨论的是数据文件。在以前处理数据的输入输出都是以终端为对象的 ,即从终端的键盘输入数据 ,运行结果显示到 显示器上。
其实有时候我们会把信息输出到磁盘上 , 当需要的时候再从磁盘上把数据读取到内存中使用 ,这里处理的就是磁盘上文件。
1.3 文件名
一个文件要有一个唯一的文件标识 ,以便用户识别和引用。文件名包含3部分:文件路径+文件名主干+文件后缀。例如: c:\code\test.txt
文件名命名有一些禁止使用的字符如:/ \ : * ? < > 空格 还有一些特殊字符。
文件名不是必须要加后缀名的。
为了方便起见 ,文件标识常被称为文件名。
1.4 二进制文件和文本文件
根据数据的组织形式 ,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储 ,如果不加转换的输出到外存 ,就是二进制文件。
如果要求在外存上以ASCII码的形式存储 ,则需要在存储前转换。 以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储 ,数值型数据既可以用ASCII形式存储 ,也可以使用二进制形式存储。
如有整数10000 ,如果以ASCII码的形式输出到磁盘 ,则磁盘中占用5个字节(每个字符一个字节), 而 二进制形式输出 ,则在磁盘上只占4个字节(VS2019测试)。
2. 文件的打开和关闭
2.1 流和标准流
2.1.1 流
我们程序的数据需要输出到各种外部设备 ,也需要从外部设备获取数据 ,不同的外部设备的输入输出操作各不相同 ,为了方便程序员对各种设备进行方便的操作 ,我们抽象出了流的概念 ,我们可以把流想象成流淌着字符的河。
C程序针对文件、 画面、键盘等的数据输入输出操作都是通过流操作的。
一般情况下 ,我们要想向流里写数据 ,或者从流中读取数据 ,都是要打开流 ,然后操作。
2.1.2 标准流
那为什么我们从键盘输入数据,向屏幕上输出数据 ,并没有打开流呢?
那是因为C语言程序在启动的时候 ,默认打开了3个流:
. stdin - 标准输入流 ,在大多数的环境中从键盘输入 ,scanf函数就是从标准输入流中读取数据。
. stdout - 标准输出流 ,大多数的环境中输出至显示器界面 ,printf函数就是将信息输出到标准输出流中。
. stderr - 标准错误流 ,大多数环境中输出到显示器界面。
这是默认打开了这三个流 ,我们使用scanf、printf等函数就可以直接进行输入输出操作的。
stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为文件指针。
C语言中 ,就是通过 FILE* 的文件指针来维护流的各种操作的。
2.2 文件指针
缓冲文件系统中 ,关键的概念是“文件类型指针” ,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区 ,用来存放文件的相关信息(如文件的名字 ,文件状态及文件当前的位置等) 。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的 ,取名FILE.
例如 ,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:
不同的C编译器的FILE类型包含的内容不完全相同 ,但是大同小异。
每当打开一个文件的时候 ,系统会根据文件的情况自动创建一个FILE结构的变量 ,并填充其中的信息 ,使用者不必关心细节。一般都是通过一个FILE*的指针来维护这个FILE结构的变量 ,这样使用起来更加方便。
下面我们可以创建一个FILE*的指针变量:
FILE* pf;
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量) 。通过该文件信息区中的信息就能够访问该文件。也就是说 ,通过文件指针变量能够间接找到与它关联的文件。
2.3 文件的打开和关闭
文件在读写之前应该先打开文件 ,在使用结束之后应该关闭文件。
在编写程序的时候 ,在打开文件的同时 ,都会返回一个FILE*的指针变量指向该文件 ,也相当于建立了指针和文件的关系。
ANSIC 规定使用 fopen 函数来打开文件 ,fclose 来关闭文件。
//打开⽂件
FILE * fopen ( const char * filename, const char * mode );
//关闭⽂件
int fclose ( FILE * stream );
const char *filename是要要打开的文件名字。
演示:
int main()
{//打开文件FILE* pf = fopen("date.txt", "w");//检查是否成功打开文件if (pf == NULL){perror("fopen");return 1;}//操作文件fputs("this is a example", pf);//关闭文件fclose(pf);return 0;
}
3. 文件的顺序读写
3.1 顺序读写函数介绍
3.1.1 fgetc 与 fputc
详见:(C语言) fgetc与fputc函数详解(本人的另一篇文章)http://t.csdnimg.cn/UYMPPhttp://t.csdnimg.cn/UYMPP
3.1.2 fgets 与 fputs
3.1.3 fprintf与 fscanf
3.1.4 fread 与 fwrite
详见(C语言)fread与fwrite详解(本人的另一篇文章)
http://t.csdnimg.cn/qIBJBhttp://t.csdnimg.cn/qIBJB
3.1.5 sscanf 与 sprintf
详解(C语言)sscanf 与 sprintf详解(本人的另一篇文章)
http://t.csdnimg.cn/7j9gJhttp://t.csdnimg.cn/7j9gJ
4. 文件的随机读写
4.1 fseek函数
根据文件指针的位置和偏移量来定位文件指针,可以自行控制光标的位置。
参数origin可以填以下的内容:
SEEK_SET表示文件的开头;
SEEK_CUR表示文件光标的当前位置;
SEEK_END表示文件的末尾;
offset是指从origin位置偏移的字符数,可正可负,正表示向后,负表示向前。
演示:
我们现在有date.txt文件,内容如图所示,我们想办法将其中的f字母读取出来。
#include <stdio.h>int main()
{FILE* pf = fopen("date.txt", "r");//将光标定位在f的位置fseek(pf, 5, SEEK_SET);int ch = fgetc(pf);printf("%c", ch);return 0;
}
这样我们从文件向后定位到了f字母,也可以从文件后面向前定位。
#include <stdio.h>int main()
{FILE* pf = fopen("date.txt", "r");//将光标定位在f的位置,从后向前定位fseek(pf, -9, SEEK_END);int ch = fgetc(pf);printf("%c", ch);return 0;
}
当然我们也可以根据需要从当前位置来偏移光标。
4.2 ftell函数
返回文件指针相对于起始位置的偏移量,返回值是long类型
演示:
#include <stdio.h>int main()
{FILE* pf = fopen("date.txt", "r");if (pf == NULL){perror("fopen");return 1;}long sz;fseek(pf, 0, SEEK_END);sz = ftell(pf);printf("size of date.txt is %ld", sz);return 0;
}
这样就返回了文件内容的内容的多少。
4.3 rewind函数
让文件指针的位置回到文件的起始位置
这个比较简单,不再过多介绍,和fseek函数
效果是一样的。
5 文件读取结束的判定
5.1 feof函数与ferror函数
feof
ferror
feof函数:当文件读取结束的时候 ,判断是读取结束的原因是否是:遇到文件尾结束。
1. 文本文件读取是否结束 ,判断返回值是否为EOF (fgetc) 或NULL (fgets) :
fgetc判断是否为EOF;
fgets判断返回值是否是NULL.;
ferror函数:当文件读取结束时,判断是不是因为发生错误而结束。
2. 二进制文件的读取结束判断 ,判断返回值是否小于实际要读的个数。
文本文件操作示例:
#include <stdio.h>
int main()
{FILE* pf = fopen("date.txt", "r");if (pf == NULL){perror("fopen");return 1;}char c;while ((c = getc(pf)) != EOF){printf("%c", c);}//下面判断文件读取是正常结束,还是发生错误结束if (feof(pf)){printf("读取到文件结尾,安全结束");}else if (ferror(pf)){printf("遇到错误");}fclose(pf);return 0;
}
二进制文件操作示例
enum
{size=5
};
#include <stdio.h>
int main()
{//将数据以二进制类型写入文件double arr1[size] = { 1,2,3,4,5 };FILE* fp = fopen("date.txt", "wb");if (fp == NULL){perror("fopen");return 1;}fwrite(arr1, sizeof(arr1[0]), size,fp);//写入完成关闭文件fclose(fp);fp = NULL;//读文件double arr2[size];fp = fopen("date.txt", "rb");size_t count = fread(arr2, sizeof * arr1, size, fp);if (count == size){puts("Array read successfully, contents: ");for (int i = 0; i < size; i++){printf("%lf ", arr2[i]);}printf("\n");}else{if (feof(fp))printf("Error reading test.bin: unexpected end of file\n");else if (ferror(fp)) perror("Error reading test.bin");}fclose(fp);return 0;
}
运行结果:
因为是二进制写入的所以我么无法看到文件的内容但是我们通过打印发现文件内容就是我们写入的double类型的1,2,3,4,5
6. ⽂件缓冲区
缓冲区的优点:
缓冲区可以减少对物理设备的直接访问次数。当数据被写入或从文件中读取时,操作系统会先将数据放入内存中的缓冲区,而不是直接写入磁盘。这样可以减少磁盘I/O操作的频率,因为多个小的I/O请求可以先在缓冲区中累积,然后一次性写入磁盘。
如果没有缓冲区,每次读写操作都可能需要等待物理设备(如硬盘或网络设备)的响应,这会导致程序运行缓慢。使用缓冲区后,程序可以先与缓冲区交互,而不必每次都等待物理设备的响应。