目录
一什么是文件
1.1程序文件:
1.2数据文件:
1.3文件名
二.文件的打开与关闭
2.1流和标准流
2.2 文件指针
2.3文件的打开与关闭
三.文件的顺序读写
3.1顺序读写函数介绍
fgetc
fputc
fgets
fputs
四.文件的随机读写
4.1 fseek
4.2 ftell
4.3 rewind
五.文件读取结束的判定
文件缓冲区
一什么是文件
文件是数据按特定格式或协议存储在外部介质上的一种数据集合。文件通常是存储在计算机存储介质上,如硬盘、光盘、磁带等,也可以是存储在外部设备或网络中。
但是在程序设计中,我们⼀般谈的文件件有两种:程序文件、数据文件(从文件功能的文件度来分类 的)。
1.1程序文件:
这类文件包含源程序文件、目标文件或可执行程序。
源程序文件:
通常以 .c、.cpp、.java 等作为后缀,它们包含程序员编写的源代码,这些源代码需要被编译器或解释器转换为可执行代码才能在计算机上运行。
目标文件:
在编译过程中产生的中间文件,例如在 Windows 环境下通常以 .obj 为后缀。这些文件包含编译器从源程序文件生成的机器语言代码,但通常还不能直接运行,需要链接器进一步处理。
可执行程序:
这是经过编译和链接后生成的文件,可以直接在计算机上运行。在 Windows 环境下,可执行程序通常以 .exe 为后缀。
1.2数据文件:
文件的内容不⼀定是程序,⽽是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本章讨论的是数据文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输⼊数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使⽤,这⾥处 理的就是磁盘上文件。
1.3文件名
⼀个文件要有⼀个唯⼀的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
二.文件的打开与关闭
2.1流和标准流
流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出 操作各不相同,为了⽅便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
C程序针对文件、画面、键盘等的数据输⼊输出操作都是通过流操作的。⼀般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。
标准流
那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语⾔程序在启动的时候,默认打开了3个流:
•stdin标准输⼊流,在大多的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。
• stdout标准输出流,大多数的环境中输出至显示器界⾯,printf函数就是将信息输出到标准输出 流中。
• stderr标准错误流,大多数环境中输出到显示器界面。
这是默认打开了这三个流,我们使用scanf、printf等函数就可以直接进行输⼊输出操作的。
stdin、stdout、stderr三个流的类型是: FILE* ,通常称为文件指针。
2.2 文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
文件指针在C语言中是一个用于指向文件的变量。这个指针通常是一个指向FILE结构体类型的指针。
VS2022编译环境提供的 stdio.h 头⽂件中有以下的⽂件类型申明:
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的指针来维护这个FILE结构的变量,这样使⽤起来更加方便。
FILE* pf;
定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是⼀个结构体变 量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与 它关联的文件。
2.3文件的打开与关闭
文件在读写之前应该先打开⽂件,在使⽤结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回⼀个FILE*的指针变量指向该文件,也相当于建立了 指针和文件的关系。
ANSIC规定使用 fopen 函数来打开文件, fclose 来关闭文件。
fopen
函数原型:
FILE *fopen(const char *filename, const char *mode);
filename:要打开或创建的文件的名称(包括路径,如果需要的话)。
mode:指定文件打开模式的字符串。这些模式字符串决定了文件应该如何被打开以及你可以如何使用这个文件。
文件的打开模式:
文件的使用方式 | 含义 | 如果指定不存在 |
“r”(只读) | 为了输⼊数据,打开⼀个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开⼀个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
rb”(只读 | 为了输入数据,打开⼀个⼆进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开⼀个⼆进制文件 | 建立一个新的文件 |
“ab”(追加) | 向⼀个⼆进制文件尾添加数据 | 建立⼀个新的文件 |
“r+”(读写) | 为了读和写,打开⼀个文本文件 | 出错 |
“w+”(读写 | 为了读和写,建议⼀个新的文件 | 建立一个新的文件 |
“a+”(读写 | 打开⼀个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开⼀个⼆进制文件 | 出错 |
“wb+”(读 写) | 为了读和写,新建⼀个新的⼆进制文件 | 建立⼀个新的文件 |
“ab+”(读 写) | 打开⼀个⼆进制文件,在文件尾进⾏读和写 | 建立⼀个新的文件 |
fclose
函数原型:
int fclose(FILE *stream);
FILE *stream:这是一个指向 FILE 对象的指针,该对象标识了要被关闭的文件。这个指针通常是通过 fopen、freopen 或 fdopen 函数返回的。
返回值:如果成功关闭了文件,fclose 返回零(0)。如果发生错误,返回非零值(EOF )。
代码实现:
#include <stdio.h>int main()
{FILE* fp;// 以写入模式打开文件 fp = fopen("test.txt", "w");if (fp == NULL) {printf("无法打开文件\n");return 1;}// 写入数据到文件 fprintf(fp, "Hello, World!\n");// 关闭文件 fclose(fp);return 0;
}
三.文件的顺序读写
3.1顺序读写函数介绍
函数名 | 功能 | 适用于 |
fgetc | 字符输⼊函数 | 所有输⼊流 |
fputc | 字符输出函数 | 所有输⼊流 |
fgets | 文本行输⼊函数 | 所有输⼊流 |
fputs | 文本行输出函数 | 所有输⼊流 |
fscanf | 格式化输⼊函数 | 所有输⼊流 |
fprintf | 格式化输出函数 | 所有输⼊流 |
fread | ⼆进制输⼊ | 文件 |
fwrite | ⼆进制输出 | 文件 |
fgetc
代码实现:
#include <stdio.h> int main()
{FILE* pf;char ch;// 打开文件 pf = fopen("test.txt", "r");if (pf == NULL) {perror(" fopen fail");return 1;}// 逐字符读取文件内容 while ((ch = fgetc(pf)) != EOF) {printf("%c",ch); // 输出读取到的字符 }// 关闭文件 if (fclose(pf) != 0) {perror("fclos fail");return 1;}return 0;
}
fputc
函数原型:
int fputc(int character, FILE *stream);
character:需要写入的字符。虽然该参数被定义为 int 类型,但实际上它只使用 int 类型的低八位,并将其作为无符号字符来处理。
stream:要写入字符的文件流。这通常是通过 fopen 函数打开的文件。
#include <stdio.h>
int main()
{FILE* pf;// 打开文件 pf = fopen("test.txt", "r");if (pf == NULL) {perror("fopen fail");return 1;}// 将字符 'A' 写入文件 fputc('A', pf);int ch = fgetc(pf);printf("读取的字符 %c",ch);// 关闭文件 if (fclose(pf) != 0) {perror("fclose fail");return 1;}return 0;
}
输出结果:
fgets
函数原型:
char *fgets(char *str, int n, FILE *stream);
str:指向一个字符数组的指针,该数组将存储从文件读取的字符串。
n:指定要读取的最大字符数(包括最后的空字符,但通常不包括换行符)。通常,这个值设置为数组的大小。
stream:指向要读取的文件的指针。返回值:
如果成功,fgets 将返回一个指向 str 的指针。
如果发生错误或到达文件末尾(EOF),fgets 将返回 NULL。
fputs
函数原型:
int fputs(const char *str, FILE *stream);
str:要写入文件的字符串。
stream:一个指向 FILE 对象的指针,该对象标识了要写入字符的流
代码实现:
#include<stdio.h>
int main()
{FILE* pf;char* arr = "hello world"; // 打开目标文件 pf = fopen("test.txt", "w"); //写入数据if (pf == NULL){perror("fopen fail");return 1;}fputs(arr, pf);//关闭文件fclose(pf);pf = fopen("test.txt", "r");//读取数据if (pf == NULL){perror("fputs fail");return 1;}char line[100];while (fgets(line, sizeof(line), pf) != NULL){printf("%s", line);}// 关闭文件 fclose(pf);return 0;
}
四.文件的随机读写
4.1 fseek
函数原型:
int fseek ( FILE * stream, long int offset, int origin );
其中,参数stream表示文件指针;offset表示要移动的字节数,可以是正数或负数;origin表示起始位置
有三种取值:
SEEK_SET | 从文件开头开始计算偏移量 |
SEEK_CUR | 从当前位置开始计算偏移量 |
SEEK_END | 从文件末尾开始计算偏移量 |
#include <stdio.h>int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL) {perror("打开文件失败"); return 1; }// 将文件指针移动到文件的第四个字节fseek(pf, 3, SEEK_SET); // SEEK_SET表示从文件开始位置计算偏移量 // 读取当前文件指针指向的字符 int ch1 = fgetc(pf); if (ch1 != EOF) {printf("第四个字符是:%c\n", ch1); }else{printf("读取第四个字符失败\n");} if (fseek(pf, -1, SEEK_END) == 0) // -1表示向前移动一个字节 {// 读取当前指针指向的字符 int ch2 = fgetc(pf); if (ch2 != EOF) {printf("文件末尾之前的字符是:%c\n", ch2); }else{printf("读取失败\n"); }}else{printf("无法定位到文件末尾之前的位置\n"); } //关闭文件fclose(pf); return 0;
}
输出结果:
4.2 ftell
函数原型:
long ftell(FILE *stream);
stream:这是一个指向 FILE 对象的指针,该对象标识了一个打开的文件流。
#include<stdio.h>
int main() {FILE* pf = fopen("test.txt", "r");if (pf == NULL) {perror("fopen");return 1;} fseek(pf,0,SEEK_END);long size = ftell(pf);// 关闭文件 fclose(pf);printf("文件长度:%ld\n",size);return 0;
}
4.3 rewind
作用是将文件内部的位置指针重新指向一个流的开头
函数原型:
void rewind ( FILE * stream );
stream为已打开文件的指针。
#include<stdio.h>
int main()
{int n;FILE* pFile;char buffer[27];pFile = fopen("myfile.txt", "w+");for (n = 'A'; n <= 'Z'; n++)fputc(n, pFile);rewind(pFile);//回到起始位置fread(buffer, 1, 26, pFile);//读取数据fclose(pFile);buffer[26] = '\0';printf(buffer);return 0;
}
五.文件读取结束的判定
被错误使⽤的 feof 牢记:在文件读取过程中,不能用feof函数的返回值直接来判断文件的是否结束。 feof 的作用是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。
1.文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
• fgetc 判断是否为 EOF 。
• fgets 判断返回值是否为 NULL 。
文件缓冲区
ANSIC标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为 程序中每⼀个正在使用的文件开辟⼀块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓 冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输 入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
#include <stdio.h>
#include <windows.h>
//VS2019 WIN11环境测试
int main()
{FILE*pf = fopen("test.txt", "w");fputs("abcdef", pf);//先将代码放在输出缓冲区 printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");Sleep(10000);printf("刷新缓冲区\n");fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘) //注:fflush 在⾼版本的VS上不能使⽤了 printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");Sleep(10000);fclose(pf);//注:fclose在关闭⽂件的时候,也会刷新缓冲区 pf = NULL;return 0;
}
刷新前:
刷新后:
这里可以得出一个结论:
因为有缓冲区的存在,C语⾔在操作文件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂ 件。 如果不做,可能导致读写文件的问题。