目录
[字符输入函数 fgetc的使用](#字符输入函数 fgetc的使用)
[字符输入函数 fputc的使用](#字符输入函数 fputc的使用)
[文本行输出函数 fputs()](#文本行输出函数 fputs())
[文本行输入函数 fgets](#文本行输入函数 fgets)
[格式化输出函数 fprintf()](#格式化输出函数 fprintf())
[格式化输入函数 fscanf()](#格式化输入函数 fscanf())
[二进制输出 fwrite](#二进制输出 fwrite)
[二进制输入 fread (从文件到内存)](#二进制输入 fread (从文件到内存))
前言 :当初在通讯录上的数据都是临时存放到内存中的,当程序运行结束的时候,所添加的数据就没有了。等到下一次运行通讯录的时候,数据又需要重新录入,我们发现这样会很繁琐,我们就想着用什么来存着这些数据一直保留着。
于是 这个问题就涉及到数据持久化 的问题了,我们一般数据持久化的方法有:数据存放到磁盘文件中、存放到数据库中等方式。所以这里就提到了文件。
一、什么是文件
平时我们说的硬盘上的文件就文件
当然在程序设计中,我们一般说到的文件有两种:程序文件、数据文件(从文件功能上来看)。
1.程序文件
- 源程序文件 (后缀 .c)
- 目标文件(.obj)
- 可执行程序(.exe)
2.数据文件
程序运行时读写的数据,需要输入输出的文件
3. 文件名
文件名包含三部分: 文件路径+文件名主干+文件后缀
例如 d:\code\test.txt
二、文件的打开和关闭
1.文件指针
在缓冲文件系统中,关键的概念是 文件类型指针 , 简称 文件指针
每个被使用的文件都在内存中开辟了一个相应的++文件信息区++ ,用来存放文件的相关信息,如(文件的名字,文件状态及文件当前的位置等),这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节
创建一个FILE*的指针变量
cpp
FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件
2.文件的打开和关闭
文件在读写之前应该先打开文件 ,在使用结束之后应该关闭文件
在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系
对于打开文件我们一般使用的是 fopen()函数,然后使用fclose()函数来关闭文件
cpp
//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );
注意当直接写文件主干时,文件是用相对路径来存放的
3.文件的打开方式
还有关于二进制文件
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
三、文件的顺序读写
按顺序进行文件的读写
1.关于输入输出,读和写
首先这是一个相对而言的,例如 相对于一个人而言 读课本是输入(就比如知识存到大脑),然后 写博客就是 输出(把大脑里面的知识输出到博客文章中)。
2.关于流的介绍
这是有些人就有一些疑问,当我们是printf , scanf 又是怎么一回事呢?
通常把显示器称为标准输出文件 printf() 就向这个文件输出数据
通常把键盘称为标准输入文件 scanf() 就向这个文件读取数据
c 语言运行起来 默认打开 三个流
- 标准输入流stdin
- 标准输入流stdout
- 标准错误流stderr
所以我们要记得关闭文件
流是一个抽象的概念
IO文件流 : 输入流:数据从文件复制到内存的过程; 输出流:数据从内存保存到文件的过程。
在IO文件流中,是相对于计算机程序中的内存来说的
3.操作文件的函数
字符输入函数 fgetc的使用
cpp
int fgetc ( FILE * stream );
这里的 int 返回值 会接收到的内容,当遇到文件末尾,读取失败会返回EOF
cpp
#include<stdio.h>
int main()
{
//使用fopen()打开文件
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);
//使用fclose()关闭文件
fclose(pf);
pf = NULL;
return 0;
}
使用stdin 从键盘上读
cpp
#include<stdio.h>
int main()
{
//使用fopen()打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = fgetc(stdin);
printf("%c\n", ch);//从键盘上读
ch = fgetc(stdin);
printf("%c\n", ch);
//使用fclose()关闭文件
fclose(pf);
pf = NULL;
return 0;
}
字符输入函数 fputc的使用
cpp
int fputc ( int character, FILE * stream );
cpp
#include<stdio.h>
int main()
{
//使用fopen()打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件,写一个字符放到流中
fputc('a',pf);
fputc('c',pf);
//使用fclose()关闭文件
fclose(pf);
pf = NULL;
return 0;
}
打开文件查看
之前介绍的stdout 也可以不写入到文中,写入到屏幕上
cpp
#include<stdio.h>
int main()
{
//使用fopen()打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件,写一个字符放到流中
//stdout 写入到屏幕上
fputc('a', stdout);
fputc('c', stdout);
//使用fclose()关闭文件
fclose(pf);
pf = NULL;
return 0;
}
上述是文件的顺序读写
文本行输出函数 fputs()
从程序输出到文件中(写)
cpp
int fputs ( const char * str, FILE * stream );
cpp
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt","w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件 一行的写
fputs("hello\n",pf);
fputs("world\n",pf);
fclose(pf);
pf = NULL;
return 0;
}
文本行输入函数 fgets
cpp
char * fgets ( char * str, int num, FILE * stream );
要木遇到最多读n - 1 个,后面再追加一个'\0',要木遇到\n 不再读了
cpp
//一行的读
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件 一行的读
char arr[10] = {0};
fgets(arr,10,pf); //这里只读了9个. 要木遇到最多读n - 1 个,后面再追加一个'\0',要木遇到\n 不再读了
printf("%s",arr);
fclose(pf);
pf = NULL;
return 0;
}
格式化输出函数 fprintf()
cpp
int fprintf ( FILE * stream, const char * format, ... );
Write formatted data to stream 写格式化数据到流里面去
cpp
#include<stdio.h>
struct S
{
int a;
float b;
};
int main()
{
FILE* pf = fopen("data.txt","w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件 从程序中写入文件
struct S s = {100,3.14f};
fprintf(pf,"%d %f",s.a,s.b);
fclose(pf);
pf = NULL;
return 0;
}
格式化输入函数 fscanf()
cpp
int fscanf ( FILE * stream, const char * format, ... );
Read formatted data from stream把带有格式的数据从流里面读出
cpp
#include<stdio.h>
struct S
{
int a;
float b;
};
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件 把文件里面的数据读到程序里面去
struct S s = {0};
fscanf(pf,"%d %f",&(s.a),&(s.b));
//这里把数据打印出来
printf("%d %f",s.a,s.b);
//这里把数据打印出来的第二种方式
//fprintf(stdout,"%d %f",s.a,s.b);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
区分:
// sprintf 和 sscanf
cpp
#include<stdio.h>
struct S
{
int a;
float b;
char str[10];
};
int main()
{
char arr[30] = { 0 };
struct S s = { 100,3.14f,"hello" };//将结构体里面的数据转换为字符串arr里面
struct S tmp = {0};
sprintf(arr, "%d %f %s", s.a, s.b, s.str);//里面的空格也会转换到arr里面
//从字符串中拿出格式化数据
sscanf(arr,"%d %f %s",&(tmp.a),&(tmp.b),tmp.str);//从字符串arr中读取格式化数据
printf("%d %f %s", tmp.a,tmp.b,tmp.str);
return 0;
}
二进制输出 fwrite
cpp
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
cpp
#include<stdio.h>
struct S
{
int a;
float b;
char str[10];
};
int main()
{
struct S s = {100,3.14f,"zz"};
FILE* pf = fopen("data.txt","wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fwrite(&s,sizeof(struct S),1,pf);
fclose(pf);
pf = NULL;
return 0;
}
打开文件 我们发现 看不明白(因为是二进制文件)
可以使用fread 来读二进制文件
二进制输入 fread (从文件到内存)
cpp
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
Read block of data from stream
cpp
//fread
#include<stdio.h>
struct S
{
int a;
float b;
char str[10];
};
int main()
{
struct S s = { 0 };
FILE* pf = fopen("data.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fread(&s,sizeof(struct S),1,pf);
//打印
printf("%d %f %s",s.a,s.b,s.str);
fclose(pf);
pf = NULL;
return 0;
}
四、文件的随机读写
这里的文件随机读写 是 想读写哪里就读写哪里
1.fseek
cpp
int fseek ( FILE * stream, long int offset, int origin );
根据文件指针的位置 和 偏移量来定位文件指针
注意origin的参数
- SEEK_SET 从文件的起始位置开始
- SEEK_CUR 从当前所指向的位置开始
- SEEK_END 从文件的末尾开始 (注意从右向左 数字为负的)
cpp
//fseek的使用
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt","r");
//文件里面的内容为abcdefghi
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf,3,SEEK_SET);//定位文件指针的指向,这里的正3表示从左到右
int ch = fgetc(pf);
printf("%c\n",ch);
fclose(pf);
pf = NULL;
return 0;
}
图解
2.ftell
返回文件指针相对于起始位置的偏移量
cpp
long int ftell ( FILE * stream );
cpp
//ftell返回文件指针相对于起始位置的偏移量
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt","r");
//文件内容是abcdefghi
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n",ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
//用ret来接收偏移量
int ret = ftell(pf);
printf("%d\n",ret);//3 这里只适用于标准输出流(屏幕)
fprintf(stdout,"%d",ret);//3 适用于所有的输出流
fclose(pf);
pf = NULL;
return 0;
}
3.rewind
让文件指针的位置 回到 文件的起始位置
cpp
void rewind ( FILE * stream );
cpp
//rewind 让文件指针的位置 回到 文件的起始位置
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
//文件内容是abcdefghi
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
//用ret来接收偏移量
int ret = ftell(pf);
printf("%d\n", ret);//3 这里只适用于标准输出流(屏幕)
//使用rewind 回到起始位置
rewind(pf);
int a = ftell(pf);// 0
fprintf(stdout,"%d",a);
fclose(pf);
pf = NULL;
return 0;
}
五、文本文件和二进制文件
文本文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件
二级制文件
数据在内存中以二进制的形式存储,如果不加转换的输出到外存
例如 十进制的10000 看下方图解
例如
cpp
#include<stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("data.txt","wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(&a,5,1,pf);//写到二进制文件中
fclose(pf);
pf = NULL;
return 0;
}
我们已经把数据以二进制形式写入到文件中,但是当我们打开时发现
这是二进制文件,我们需要用特殊的编辑器来查看,就比如使用 Visual Studio 2022 来进行查看
下面是查看方法:
第一步 先 右击 源文件 - 添加 - 现有项
第二步 找到文本并添加进去
这时就会看到
第三步 右击 文件 - 点击打开方式
第四步 选择二进制编辑器
然后就有了
六、文件读取结束的判定
- fgetc 判断是否为EOF
- fgets 判断返回值是否为NULL
- fread 判断返回值是否小于实际要读的个数
在这里 要多说一个 feof
feof: 用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束
即 feof 用来判断是什么原因结束的
七、文件缓冲区
文件缓冲区是指在操作系统中,将文件读取或写入时,先将数据缓存到内存中的一段空间,等待一定量的数据积累后再进行实际的读写操作 。这样做的好处是可以提高文件输入和输出效率,减少文件系统的负担。而且通过缓冲,可以减少磁盘或网络的读写次数,降低系统负担,提高程序的性能。
文件缓冲区包括输入缓冲区和输出缓冲区。输入缓冲区用于存储等待处理的输入数据,而输出缓冲区用于存储等待写入文件的数据。当输入缓冲区或输出缓冲区已满或达到一定数量时,系统才会进行实际的读写操作。当文件操作完成时,缓冲区的内容会被写入或者读取出来,或者在程序运行结束时被自动释放。
在编程中,我们可以通过控制文件缓冲区大小和刷新缓冲区来提高程序效率。如果希望立即将缓冲区的数据写入磁盘或读取最新数据,可以使用flush()、fflush()等函数来强制刷新缓冲区。
cpp
#include<stdio.h>
#include<windows.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//
fprintf(pf,"hello\n");
fflush(pf); //刷新后就会出现在文件中
Sleep(10000);//睡眠10秒
fprintf(pf,"world");
fclose(pf);
pf = NULL;
return 0;
}