C语言-> 文件操作(函数满屏)
温馨提示:这篇文章已超过460天没有更新,请注意相关的内容是否还可用!
系列文章目录
前言
✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉
🍎个人主页:橘橙黄又青_C语言,数据结构,函数-CSDN博客
目的:学习文件操作,即文件相关函数的学习
在这里首先放置我个人认为好的学习c语言的网站
:cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
好了我们现在开始吧?
1. 为什么使⽤⽂件?
如果没有⽂件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失 了,等再次运⾏程序,是看不到上次程序的数据的,如果要将数据进⾏持久化的保存,我们可以使⽤⽂件。2. 什么是⽂件?
磁盘上的⽂件是⽂件。 但是在程序设计中,我们⼀般谈的⽂件有两种: 程序⽂件、数据⽂件 (从⽂件功能的⻆度来分类 的)。2.1 程序⽂件
程序⽂件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows 环境后缀为.exe)。2.2 数据⽂件
⽂件的内容不⼀定是程序,⽽是程序运⾏时读写的数据,⽐如程序运⾏需要从中读取数据的⽂件,或者输出内容的⽂件本章讨论的是数据⽂件。 在以前各章所处理数据的输⼊输出都是以终端为对象的,即从终端的键盘输⼊数据,运⾏结果显⽰到 显⽰器上。 其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使⽤,这⾥处 理的就是磁盘上⽂件。2.3 ⽂件名
⼀个⽂件要有⼀个唯⼀的⽂件标识,以便⽤⼾识别和引⽤。 ⽂件名包含3部分:⽂件路径+⽂件名主⼲+⽂件后缀 比如: c:\ code \ test . txt3. ⼆进制⽂件和⽂本⽂件
根据数据的组织形式,数据⽂件被称为⽂本⽂件或者⼆进制⽂件 ⼆进制⽂件:数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存,就是⼆进制⽂件。 ⽂本⽂件: 以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的⽂件就是⽂ 本⽂件。 那⼀个数据在内存中是怎么存储的呢? 字符⼀律以ASCII形式存储,数值型数据既可以⽤ASCII形式存储,也可以使⽤⼆进制形式存储,什么意思比如说: 如有整数10000,如果以ASCII码的形式输出(把10000当作字符)到磁盘,则磁盘中占⽤5个字节(每个字符⼀个字节),⽽ ⼆进制形式输出,则在磁盘上只占4个字节(VS2019测试)。 展示:
测试代码:
#include
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);//⼆进制的形式写到⽂件中
fclose(pf);
pf = NULL;
return 0;
}
在vs2022上
以二进制的方式打开,我们来看看:
那我们以
文本的形式打开:
好啦,理解二进制文件和文本文件之后,接下来我们学习文件的打开和关闭。
4. ⽂件的打开和关闭
在此之前先了解一个抽象的概念:
4.1 流和标准流
4.1.1 流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出 操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流 想象成流淌着字符的河。 C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。 ⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。4.1.2 标准流
那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语⾔程序在启动的时候,默认打开了3个流: • stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。 • stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出流中。 • stderr - 标准错误流,⼤多数环境中输出到显⽰器界⾯。 这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为⽂件指针。
C语⾔中,就是通过 FILE* 的⽂件指针来维护流的各种操作的 。 这怎么理解呢?来看图:
相当于中间商处理翻译客户的文件,再把以客户熟知方式的输出还给客户。
4.2 ⽂件指针
缓冲⽂件系统中,关键的概念是“⽂件类型指针”,简称“⽂件指针”。 每个被使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如⽂件的名 字,⽂件状态及⽂件当前的位置等)。这些 信息是保存在⼀个结构体变量中的。该结构体类型是由系 统声明的,取名FILE。 例如,VS2013编译环境提供的 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指向某个⽂件的⽂件信息区(是⼀个结构体变 量)。通过该⽂件信息区中的信息就能够访问该⽂件。 也就是说,通过⽂件指针变量能够间接找到与 它关联的⽂件 。 可以这样理解:
好了,接下来我们学校有关文件的函数。
4.3 ⽂件的打开和关闭
⽂件在读写之前应该先打开⽂件,在使⽤结束之后应该关闭⽂件。 在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了 指针和⽂件的关系。 ANSIC 规定使⽤ fopen 函数来打开⽂件, fclose 来关闭⽂件 。//打开⽂件 FILE * fopen ( const char * filename, const char * mode );//两个参数:一个是文件名,一个是文件使用方式 //关闭⽂件 int fclose ( FILE * stream );mode表⽰⽂件的打开模式,下⾯都是⽂件的打开模式下的操作:
| ⽂件使⽤⽅式 | 含义 | 如果指定⽂件不存在 |
| “r”(只读) | 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 | 出错 |
| “w”(只写) | 为了输出数据,打开⼀个⽂本⽂件 | 建⽴⼀个新的⽂件 |
| “a”(追加) | 向⽂本⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
| “rb”(只读) | 为了输⼊数据,打开⼀个⼆进制⽂件 | 出错 |
| “wb”(只写) | 为了输出数据,打开⼀个⼆进制⽂件 | 建⽴⼀个新的⽂件 |
| “ab”(追加) | 向⼀个⼆进制⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
| “r+”(读写) | 为了读和写,打开⼀个⽂本⽂件 | 出错 |
| “w+”(读写) | 为了读和写,建议⼀个新的⽂件 | 建⽴⼀个新的⽂件 |
| “a+”(读写) | 打开⼀个⽂件,在⽂件尾进⾏读写 | 建⽴⼀个新的⽂件 |
| “rb+”(读写) | 为了读和写打开⼀个⼆进制⽂件 | 出错 |
| “wb+”(读 写) | 为了读和写,新建⼀个新的⼆进制⽂件 | 建⽴⼀个新的⽂件 |
| “ab+”(读 写) | 打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写 | 建⽴⼀个新的⽂ |
我们怎么理解读和写看图:
这里我们演示一下打开关闭,实战代码:
/* fopen fclose example */
#include
int main ()
{
FILE * pFile;
//打开⽂件
pFile = fopen ("myfile.txt","w");
//⽂件操作
if (pFile!=NULL)
{
fputs ("fopen example",pFile);
//关闭⽂件
fclose (pFile);
}
return 0;
}
5. ⽂件的顺序读写
5.1 顺序读写函数介绍
| 函数名 | 功能 | 适⽤于 |
| fgetc | 字符输⼊函数 | 所有输出流 |
| fputc | 字符输出函数 | 所有输出流 |
| fgets | ⽂本⾏输⼊函数 | 所有输出流 |
| fputs | ⽂本⾏输出函数 | 所有输出流 |
| fscanf | 格式化输⼊函数 | 所有输出流 |
| fprintf | 格式化输出函数 | 所有输出流 |
| fread | ⼆进制输⼊ | ⽂件 |
| fwrite | ⼆进制输出 | ⽂件 |
那好我们来一个一个介绍:打开网站:
https://legacy.cplusplus.com/reference/clibrary/
(1)fputc
参数是:文件名和流
代码实现:
#include
#include
int main()
{
FILE* pf = fopen("data.txt", "w");//写入文件
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputc('a', pf);//写入一个字符'a'
fputc('\n', pf);
fputc('b', pf);
fclose(pf);
return 0;
}
fputc只能一个一个字符的输入,也可以写一个循环输入多个字符。
for (i = 0; i(2)fgetc
现在先我们输入字符进“data.txt”文件中,比如说输入:“abcdefg",然后实现代码读操作
代码:
#include #include int main() { FILE* pf = fopen("data.txt", "r");//写入文件 if (pf == NULL) { perror("fopen"); return 1; } int ch = fgetc(pf);//读取一个字符 printf("%c", ch);//打印 fclose(pf); pf = NULL; return 0; }具体操作和输出结果如下:
当然也可以写成循环的形式,但是要注意什么时候结束,比如说:
下面代码:
#include #include int main() { FILE* pf = fopen("data.txt", "r");//写入文件 if (pf == NULL) { perror("fopen"); return 1; } int ch = 0; while ((ch = fgetc(pf)) != ' ') { printf("%c", ch);//打印 } fclose(pf); pf = NULL; return 0; }两种情况:
这就要看文件文本句末了,避免造成死循环。
接下来我们学习一个复制文件内容的操作:
假设文件里面有代码
代码实现:
//从data.txt中读取数据 //写到data2.txt的文件中 #define _CRT_SECURE_NO_WARNINGS #include #include int main() { FILE* pfread = fopen("data.txt", "r");//读 if (pfread == NULL) { perror("fopen->data1.txt");//报错详细一点 return 1; } FILE* pfwrite = fopen("data2.txt", "w");//写 if (pfwrite == NULL) { fclose(pfread);//如果出现错误先关闭打开文件 pfread = NULL; perror("fopen->data2.txt"); return 1; } //数据的读写(拷贝) int ch = 0; while ((ch = fgetc(pfread)) != EOF) { fputc(ch, pfwrite);//把ch写入pfwrite } fclose(pfread); fclose(pfwrite); return 0; }输出结果:
(3)fputs
代码实现:
#include #include int main() { FILE* pf = fopen("data.txt", "w"); if (pf == NULL) { return 1; } //写文件 - 写一行 fputs("abcdef\n", pf); fputs("abcdef\n", pf); fputs("abcdef\n", pf); fputs("abcdef\n", pf); fclose(pf); pf = NULL; return 0; }输出结果:
(4)fgets
三个参数:
1. 输入地的指针
2.输入个数,但是要-1因为最后要输入\0
3.流
代码实现:
#include #include int main() { FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { return 1; } //读取 char arr[20] = "xxxxxxxxxxxxxxx"; fgets(arr, 10, pf);//从流中读取9个字符(有一个放入\0)放入arr中 fclose(pf); pf = NULL; return 0; }输出结果:
当然也可以输出到屏幕上:
看代码:
#include #include int main() { FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { return 1; } //读取 char arr[20] = "xxxxxxxxxxxxxxx"; fgets(arr, 10, stdin);//标准输入流 fputs(arr, stdout);//标准输出流 fclose(pf); pf = NULL; return 0; }输出结果:
这里要注意的是输入流和输出流在函数的位置。
(5)fscanf和fprintf
fprintf代码实现:
#include #include struct Stu { char name[20]; int age; float score; }; int main() { struct Stu s = { "zhangsan", 20, 90.5f }; FILE*pf = fopen("data.txt", "w"); if (pf == NULL) { return 1; } //写文件 //printf(""%s %d %.1f", s.name, s.age, s.score");//很相似 fprintf(pf, "%s %d %.1f", s.name, s.age, s.score); // fclose(pf); pf = NULL; return 0; }输出结果:
同理:fscanf
假设文件里有:
代码:
#include #include struct Stu { char name[20]; int age; float score; }; int main() { struct Stu s = {0}; FILE* pf = fopen("data.txt", "r");//读 if (pf == NULL) { return 1; } //写读文件 fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score)); fprintf(stdout, "%s %d %.1f\n", s.name, s.age, s.score);//输出屏幕 // fclose(pf); pf = NULL; return 0; }输出结果:
6. fwrite二进制输出
首先我们先来学习一下这个函数:
代码实现:
#include #include struct Stu { char name[20]; int age; float score; }; int main() { struct Stu s = {"zhangsan", 20, 90.5}; FILE* pf = fopen("data.txt", "wb");//wb是以二进制的方式写入 if (pf == NULL) { return 1; } //二进制的形式写文件 fwrite(&s, sizeof(s), 1, pf); fclose(pf); pf = NULL; return 0; }输出:
这里是以二进制输入的文本翻译成这样,是因为文本不具备二进制翻译,但是如果以二进制输出是和输入一样的结果,下面我们把刚刚以二进制输入文件以二进制输出看看。
7.fread以二进制输出
这里我们可以看到,参数和上面的fwite是一样的。
代码实现:
#include #include struct Stu { char name[20]; int age; float score; }; int main() { struct Stu s = {0}; FILE* pf = fopen("data.txt", "rb");//rb以二进制形式读进 if (pf == NULL) { return 1; } //二进制的形式du文件 fread(&s, sizeof(s), 1, pf); printf("%s %d %lf", s.name , s.age, s.score ); fclose(pf); pf = NULL; return 0; }输出结果:
在这里我们在认识两个函数sscanf和sprintf.
8.sscanf和sprintf.
对比:
使用:
#include struct S { char name[20]; int age; float score; }; int main() { struct S s = { "zhangsan", 20, 85.5f }; struct S tmp = { 0 }; char arr[100] = { 0 }; sprintf(arr, "%s %d %f", s.name, s.age, s.score);//把结构体s的数据输入arr printf("%s\n", arr); // sscanf(arr, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));//把arr结构体数据输入tmp printf("%s %d %f\n", tmp.name, tmp.age, tmp.score); return 0; }这里要慢慢理解,
6. ⽂件的随机读写
6.1 fseek和ftell
根据⽂件指针的位置和偏移量来定位⽂件指针。
什么意思,来
6.2 ftell
返回⽂件指针相对于起始位置的偏移量。
函数内容:
long int ftell ( FILE * stream );现在我们来实现fseek和ftell
代码展示:
#include int main() { FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } int ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch);//让光标指向d int n = ftell(pf);//计算相对起始位置的偏移量并保存 printf("%d\n", n); fclose(pf); pf = NULL; return 0; }输出结果:
再看看
这个:
#include int main() { FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } int ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch);//让光标指向d int n = ftell(pf);//计算相对起始位置的偏移量并保存 printf("%d\n", n); fseek(pf, -4, SEEK_CUR);//当前位置 printf("%c\n", ch); fclose(pf); pf = NULL; return 0; }输出结果:
怎么理解:
6.3 rewind
作用:让⽂件指针的位置回到⽂件的起始位置
代码展示:
#include //data.txt里面有abcdefg int main() { FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } int ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch);//光标指向c rewind(pf);//让⽂件指针的位置回到⽂件的起始位置 ch = fgetc(pf); printf("%c\n", ch); fclose(pf); pf = NULL; return 0; }输出结果:
7. ⽂件读取结束的判定
7.1 被错误使⽤的 feof
牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。 feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。 1. ⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets ) 例如: • fgetc 判断是否为 EOF . • fgets 判断返回值是否为 NULL . 2. ⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。 比如: • fread判断返回值是否⼩于实际要读的个数。 ⽂本⽂件的例⼦:#include #include int main(void) { int c; // 注意:int,⾮char,要求处理EOF FILE* fp = fopen("test.txt", "r"); if (!fp) { perror("File opening failed"); return EXIT_FAILURE;//1 } //fgetc 当读取失败的时候或者遇到⽂件结束的时候,都会返回EOF while ((c = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环 { putchar(c); } //判断是什么原因结束的 if (ferror(fp))//遇到错误结束 puts("I/O error when reading"); else if (feof(fp))//遇到文末结束 puts("End of file reached successfully"); fclose(fp); }⼆进制⽂件的例⼦:#include enum { SIZE = 5 }; int main(void) { double a[SIZE] = { 1.,2.,3.,4.,5. }; FILE* fp = fopen("test.bin", "wb"); // 必须⽤⼆进制模式 fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组 fclose(fp); double b[SIZE]; fp = fopen("test.bin", "rb"); size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组 if (ret_code == SIZE) {//判断返回值是否⼩于实际要读的个数 puts("Array read successfully, contents: "); for (int n = 0; n8. ⽂件缓冲区
ANSIC 标准采⽤“缓冲⽂件系统”处理的数据⽂件的,所谓缓冲⽂件系统是指 系统⾃动地在内存中为 程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓 冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输 ⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区 (程序变量等)。缓 冲区的⼤⼩根据C编译系统决定的。例子体验缓冲区的存在:
#include #include //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语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂件,如果不做,可能导致读写⽂件的问题。 好啦今天就到这里了,都看到这里了,点一个赞吧,感谢观看。
































