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