作者前言
🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂
文件的相关操作
- **作者前言**
- 使用文件的意义
- 什么是文件
- 程序文件
- 数据文件
- 操作对象
- 文件名
- 文件的打开和关闭
- 文件指针
- 文件的打开和关闭
- 文件的顺序读写
- 字符输出函数
- 字符输入函数
- 文本字符串输出函数
- 文本字符串输入函数
- 格式化输出函数
- 格式化输入函数
- 二进制输出函数
- 二进制输入函数
- sprintf(将格式化的数据转换成字符串)
- 将字符串转换成格式化的数据(sscanf)
- 文件的随机读写
- fseek(控制光标的位置)
- 计算起始位置到当前位置的偏移量(ftell)
- 让文件指针的位置回到文件的起始位置(rewind)
- 文件的介绍
- 文件读取结束的判定
- feof
- 文件缓冲区
使用文件的意义
前面我们写过很多的代码,运行了很多的程序,但是一旦程序结束了或者断电了,这数据就会不见了,因为这些数据是存储在内存中的,但是使用文件可以有效防止
文件是存在硬盘的
什么是文件
在程序设计中,我们一般谈论两种:一种是程序文件,另一种是数据文件
程序文件
包含有源程序文件(如.c)、目标文件(.obj)、 可执行文件(.exe)
数据文件
数据文件就是被读取数据或者写入数据的文件,或者我们可以理解为我们操作哪个文件哪个文件就是数据文件
操作对象
前面我们使用scanf和printf所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显
示器上。
现在我们把键盘和屏幕换成了文件,输入就是读取文件内容, 输出就是往文件里面写入数据,
这里的程序中的数据是保存在内存中的
文件名
一个文件要用一个唯一的文件标识符,以便用户识别和引用
文件名包含三部分:文件路径+文件名主干+文件后缀
例如: C:\a\b.c
C:\a\是文件路径 ,b是文件主干 .c是文件后缀
文件的打开和关闭
文件指针
C语言中每个文件的使用都会在内存开辟一块空间,也就是文件信息区,用来存放文件的相关信息,这些信息是保存在一个名叫FILE类型的结构体变量中,这个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*类型的指针来接收这个FILE类型的结构体变量的地址
FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件
那我们怎么找到这个变量的地址呢?,当我们打开一个文件的时候就会创建一个文件信息区,并且自动填充相关信息
文件的打开和关闭
打开
fopen 函数返回一个指向 FILE 结构体的指针,该结构体用于表示打开的文件。如果打开文件失败,函数将返回 NULL。在使用完打开的文件后,应该使用 fclose 函数关闭文件
filename:文件名
mode:打开方式
这两个参数只需我们传入两个字符串常量就可以,传入字符串常量就会把第一个字符的地址传入进去
打开方式如下:
关闭
需要注意的是,fclose 函数将文件缓冲区中的数据写入磁盘,并释放与该文件相关的所有资源。在关闭文件之前,应该确保已完成对文件的所有操作。
如果流成功关闭,则返回零值。
失败时,将返回 EOF。
#include<stdio.h>
int main()
{
FILE *p = fopen("./test.txt", "w");
if (p == NULL)
{
perror("fopen");
return 1;
}
int b = fclose(p);
printf("%d", b);
p = NULL;
return 0;
}
文件的顺序读写
前面我们使用scanf和printf是在终端操作的,如果我们要在文件上操作就不能使用这些函数
我们要站在内存的角度思考,写入内存的数据叫输入,从内存中输出数据叫输出
这个图是一个简便的,文件到程序和文件到程序都不是一步到位的,都要经过一个流,
本质上我们是操作流来操作文件,我们使用scanf和printf可以直接操作数据,是因为,C语言程序只要运行起来就会打开三个流 :标准输入流(stdin) 、标准输出流(stdout)、 标准错误流(stderr)
stdin、stdout 、stderr的类型都是FILE*类型的
输出流(把数据写入到流,而不是写入到文件)(对象是屏幕):
#include<stdio.h>
#include<stdlib.h>
int main()
{
//打开文件
FILE* file = fopen("./test.txt", "w");
if (file == NULL)
{
perror("fopen");
return 1;
}
char str;
int a = 0;
while ( a < 10)
{
scanf("%c", &str);
getchar();
fputc(str, stdout);
a++;
}
fclose(file);
file = NULL;
return 0;
}
输入流(从流中获取数据,而不是从文件里面获取)(对象是键盘):
#include<stdio.h>
#include<stdlib.h>
int main()
{
//打开文件
FILE* file = fopen("./test.txt", "w");
if (file == NULL)
{
perror("fopen");
return 1;
}
char str;
str = fgetc(stdin);
printf("%c", str);
fclose(file);
file = NULL;
return 0;
}
文件流:文件变量的地址,也就是fopen返回的地址
字符输出函数
fputc 函数将字符写入文件后,文件指针会自动向后移动一个字符位置。如果写入成功,函数将返回写入的字符;否则,返回 EOF。
#include<stdio.h>
#include<stdlib.h>
int main()
{
//打开文件
FILE* file = fopen("./test.txt", "w");
if (file == NULL)
{
perror("fopen");
return 1;
}
char str;
int a = 0;
while ( a < 10)
{
scanf("%c", &str);
getchar();
fputc(str, file);
a++;
}
fclose(file);
file = NULL;
return 0;
}
字符输入函数
stream是一个指向文件的指针,指向我们要读取的文件流。函数返回值是读取的字符的ASCII码值,如果到达文件末尾或者读取出错,返回EOF。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE* file = fopen("./test.txt", "r");
if (file == NULL)
{
perror("fopen");
return 1;
}
char a;
int b = 0;
while (b < 5)
{
a = fgetc(file);
printf("%c ", a);
b++;
}
fclose(file);
file = NULL;
return 0;
}
文本字符串输出函数
,str是要写入的字符串,stream是一个指向文件的指针,指向我们要写入的文件流。函数返回值为非负整数表示成功,如果写入出错,返回EOF。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE* file = fopen("./test.txt", "w");
if (file == NULL)
{
perror("fopen");
return 1;
}
char arr[30] = "qwertytuio";
int a = fputs(arr, file);
printf("%d", a);
fclose(file);
file = NULL;
return 0;
}
文本字符串输入函数
str是一个指向字符数组的指针,用于存储读取到的字符串;n是要读取的最大字符数(包括空字符);stream是一个指向文件的指针,指向我们要读取的文件流。函数返回值是一个指向存储读取到的字符串的指针,如果到达文件末尾或者读取出错,返回NULL。
需要注意的是在文件中真正读取的字符是 n - 1,或者读取到\n或者读完文件内容就不会继续读下去
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE* file = fopen("./test.txt", "r");
if (file == NULL)
{
perror("fopen");
return 1;
}
char arr[20];
fgets(arr, 5, file);
printf(arr);
fclose(file);
file = NULL;
return 0;
}
格式化输出函数
如果我们对比printf函数就会发现.fprintf多了stream这个参数,
stream是一个指向文件的指针,指向我们要写入的文件流;format是一个格式化字符串,用于指定写入数据的格式;…表示可变参数,用于提供要写入的具体数据。
成功后,将返回写入的字符总数。
如果发生写入错误,则设置错误指示器(ferror)并返回负数。
如果在写入宽字符时发生多字节字符编码错误,errno 将设置为 EILSEQ 并返回负数。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE* file = fopen("./test.txt", "w+");
if (file == NULL)
{
perror("fopen");
return 1;
}
fprintf(file, "%d\n", 123456789);
char arr[11];
fclose(file);
file = NULL;
//光标回复
FILE* file1 = fopen("./test.txt", "r");
if (file1 == NULL)
{
perror("fopen");
return 1;
}
fgets(arr, 10, file1);
printf(arr);
fclose(file1);
file1 = NULL;
return 0;
}
格式化输入函数
如果和scanf函数对比就会发现fscanf只是多了stream参数
stream 是指向要读取的文件的指针,format 是格式字符串,用于指定要读取的数据类型和格式。
成功后,该函数返回成功填充的参数列表的项数。此计数可以与预期的项目数匹配,也可以由于匹配失败、读取错误或文件末尾的到达而减少(甚至为零)。
如果发生读取错误或在读取时到达文件末尾,则会设置正确的指示器(feof 或 ferror)。并且,如果在成功读取任何数据之前发生任一情况,则返回 EOF。
如果在解释宽字符时发生编码错误,该函数会将 errno 设置为 EILSEQ。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE* file = fopen("./test.txt", "r");
if (file == NULL)
{
perror("fopen");
return 1;
}
int a;
fscanf(file, "%d", &a);
printf("%d", a);
fclose(file);
file = NULL;
file = fopen("./test.txt", "w");
if (file == NULL)
{
perror("fopen");
return 1;
}
fprintf(file, "%d", 11111111);
fclose(file);
file = NULL;
return 0;
}
二进制输出函数
上面的函数是使用在所有输入流和输出流的函数(包含有标准输入、输出流和文件流),一般我们只是使用整形或者字符来写入和输出,只能解决一些,但是如果遇见二进制就不一定行了,下面介绍两个函数用于二进制文件的读写操作
ptr 是指向要写入文件的数据的指针,size 是每个数据项的大小(以字节为单位),count 是要写入的数据项的数量,stream 是指向要写入的文件的指针。
返回成功写入的元素总数。
int main()
{
FILE *file = fopen("./test.txt", "wb");
if (file == NULL)
{
perror("fopen");
return 1;
}
int arr[10] = {65,67,68,69,70,71,72,73,74};
fwrite(arr, 4, 9, file);
fclose(file);
file = NULL;
return 0;
}
二进制输入函数
ptr 是指向要存储读取数据的内存区域的指针,size 是每个数据项的大小(以字节为单位),count 是要读取的数据项的数量,stream 是指向要读取的文件的指针。
返回成功读取的元素总数。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *file = fopen("./test.txt", "rb");
if (file == NULL)
{
perror("fopen");
return 1;
}
int arr[5];
fread(arr, 4, 5, file);
fclose(file);
file = NULL;
return 0;
}
sprintf(将格式化的数据转换成字符串)
sprintf 是 C 语言标准库中的一个函数,用于将格式化的数据写入字符串。它的使用方式与 printf 类似,但是 sprintf 将输出写入到指定的字符串中,而不是标准输出(屏幕)简单理解就是把屏幕换成字符串中
str 是指向输出字符串的指针,format 是格式化字符串,后面的 … 表示可变参数列表,用于填充格式化字符串中的占位符。sprintf 的返回值是输出到字符数组中的字符数(不包括空字符)
#include<stdio.h>
#include<stdlib.h>
int main()
{
char arr[10] = "123456789";
char arr1[10] = "1111111";
int a = 100;
sprintf(arr, "%s", arr1);
sprintf(arr, "%d", a);
return 0;
}
就是把arr1的数据写入到arr
将字符串转换成格式化的数据(sscanf)
str 是要读取的字符串,format 是格式化字符串,后面的 … 表示可变参数列表,用于接收读取到的数据。sscanf 的返回值是成功读取并赋值的参数个数
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
char arr[] = "1000 100 100";
int arr1[5];
int b;
sscanf(arr, "%d %d %d", arr1, arr1 + 1, arr1 + 2);
return 0;
}
把arr的数据以int类型写入到arr1中
文件的随机读写
fseek(控制光标的位置)
其中,stream 是一个指向文件的指针,offset 是要定位的偏移量(左负右正),origin 是定位的起始位置。origin 可以取以下值之一:
SEEK_SET:从文件开头开始计算偏移量。
SEEK_CUR:从当前位置开始计算偏移量。
SEEK_END:从文件末尾开始计算偏移量。
fseek 的返回值为 0 表示成功,非零值表示失败。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
FILE *file = fopen("./test.txt", "r+");
if (file == NULL)
{
perror("fopne");
return 1;
}
char a;
char arr[5];
//读文件
a = fgetc(file);
fseek(file, -1, SEEK_CUR);//当前位置向左移动
fgets(arr, 5, file);
printf("%c\n", a);
printf(arr);
fclose(file);
file = NULL;
return 0;
}
计算起始位置到当前位置的偏移量(ftell)
stream 是一个指向文件的指针。ftell 的返回值是当前文件指针相对于文件开头的偏移量,如果发生错误则返回 -1。
include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
FILE* file = fopen("./test.txt", "r");
if (file == NULL)
{
perror("fopen");
return 1;
}
char arr[10];
fgets(arr, 5, file);
int a = ftell(file);
printf("%d", a);
fclose(file);
file = NULL;
return 0;
}
让文件指针的位置回到文件的起始位置(rewind)
stream 是一个指向文件的指针。rewind 函数会将文件指针移动到文件的开头,相当于调用 fseek(stream, 0, SEEK_SET)。
include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
FILE* file = fopen("./test.txt", "r");
if (file == NULL)
{
perror("fopen");
return 1;
}
char arr[10];
fgets(arr, 5, file);
rewind(file);
int a = ftell(file);
printf("%d", a);
fclose(file);
file = NULL;
return 0;
}
文件的介绍
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。
这张图是不使用二进制输入的结果
这张图就是二进制输入的结果,有小端存储
文件读取结束的判定
feof
这个函数不是用来判断文件是否结束的,很多人误以为这个使用判断文件是否结束的,这种是错误的
文件结束的情况是多种的,而feof函数是用来判断文件结束是否是读取到文件末尾而结束
检查文件结束指示器
如果设置了与流关联的文件结束指示符,则返回非零值。
否则,返回零。
简单理解就是,当文件遇见文件结束标准 就会返回非0值,否则就会返回0
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
FILE* pf = fopen("./test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
FILE* pf1 = fopen("./test1.txt", "w");
if (pf1 == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
fputc(ch, pf1);
}
if (feof(pf))
{
puts("正常文件结束\n");
}
fclose(pf);
pf = NULL;
fclose(pf1);
pf1 = NULL;
return 0;
}
perror用于输出文件相关的错误信息,而feof用于判断文件指针是否到达文件末尾。
perror主要用于处理文件操作中的错误,而feof主要用于控制文件读取的循环条件。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
FILE* pf = fopen("./test.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int arr1[10] = { 0 };
int num = fread(arr1, sizeof * arr1, sizeof(arr1) / sizeof *arr1, pf);
if (num == sizeof(arr1) / sizeof * arr1)
{
while (num)
{
printf("%d ", arr1[--num]);
}
}
else
{
if (ferror(pf))
{
puts("文件读取到错误\n");
}
else if(feof(pf))
{
printf("文件正常结束\n");
}
}
fclose(pf);
pf = NULL;
return 0;
}
ferror用于检测文件操作函数是否发生了错误,而feof用于判断文件指针是否到达文件末尾。
ferror主要用于处理文件操作中的错误,而feof主要用于控制文件读取的循环条件。
文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的
文件缓冲区是在内存上的
输入输出流(Input/Output Stream)是指用于数据传输的抽象概念。在计算机中,输入流用于从外部获取数据,输出流用于向外部发送数据。输入流和输出流可以与不同的设备或资源进行交互,如键盘、文件、网络等。
缓冲区(Buffer)是用于临时存储数据的内存区域。在输入输出操作中,缓冲区通常与输入输出流相关联。当进行输入操作时,数据会先存储到输入流的缓冲区中;而进行输出操作时,数据会先存储到输出流的缓冲区中。缓冲区的存在可以提高输入输出的效率,减少对底层设备的频繁访问。
使用缓冲区的好处是可以批量处理数据,减少对底层设备的读写次数。当缓冲区被填满或达到一定条件时,数据会被一次性地写入或读取。这样可以提高性能和效率。缓冲区的大小通常是固定的,根据应用程序的需求进行设置。
在C语言中,标准库提供了一系列函数来进行输入输出操作,如fread、fwrite、fgets、fputs等。这些函数会自动将数据从缓冲区读取或写入到输入输出流中。同时,我们也可以使用fflush函数来手动刷新缓冲区,确保数据及时地写入或读取。
总结:
输入输出流用于数据传输,输入流用于获取数据,输出流用于发送数据。
缓冲区是临时存储数据的内存区域,与输入输出流相关联。
缓冲区可以提高输入输出操作的效率,减少对底层设备的频繁访问。
在C语言中,使用标准库函数进行输入输出操作,并自动将数据读取或写入到缓冲区中。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<windows.h>
int main()
{
FILE* pf = fopen("./test1.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;
}
在缓冲区中要想写入数据到文件有两者方法
1.缓冲区满了或者刷新缓冲区会把数据写入到文件
2.程序结束,会把数据写入到文件