摘要:二什么是文件磁盘上的文件就是文件。文件指针变量定义是一个指向类型数据的指针变量。表示向何种流中输出,可以是标准输出流,也可以是文件流。文件结构体指针,将要读取的文件流。
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
我们前面学习结构体时,写了通讯录的程序,当通讯录运行起来时,可以给通讯录增加、删除数据,此时的数据是存放在内存中的,当程序退出时,通讯录的数据就被销毁了,下次运行通讯录时,数据又得重新录入,如果使用这样的通讯录就会非常的坐牢。
所以这些通讯录数据我们仅仅放在内存里是不行的,大家都知道,我们电脑的C盘、D盘、E盘等等里面存放的文件,你不进行删除,它就一直在那里,那我们可以试着把通讯录的数据存入磁盘里,来实现数据的持久性。
磁盘上的文件就是文件。
但是在程序设计中,我们一般谈的文件有两种:
1.程序文件
2.数据文件
(以文件功能来划分)
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本文着重讨论数据文件
在以前各章所处理的数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行的结果显示到显示器上,其实有时候我们会把信息输出到键盘上,当需要的时候再从键盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件。
下图是数据文件与程序文件的交互简图
一个文件要有一个唯一的文件标识,便于用户的识别与引用。
文件名包含3个部分:文件路径+文件名主干+文件后缀
栗子:E:/c-language-notes/test.txt
这里的E:/c-language-notes/叫作文件路径,是在E盘c-language-notes这个路径底下
test叫作文件主干
.txt叫做文件后缀
为了方便起见,文件标识常被称为文件名
缓冲文件系统中,关键的概念是“文件类型指针”,简称文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,结构体类型名为FILE(下文会提到)
如上图,当你想要操作一个数据文件时,你不可避免地会经历3个操作:打开文件、读/写文件、关闭文件。只要你打开文件,系统会自动生成一个叫做文件信息区的东西,也就是会创建一个 FILE 类型结构体变量,上图以f作为示例,实际也可能是其他的,那么创建完成后,f就会和data.txt文件强关联了,f会记录data文件名、文件有多大、文件在哪个位置、文件的状态是怎样的。。。
vs2013的编译器环境提供的stdio.h的头文件中有以下的文件类型声明:
struct _iobuf{ char*_ptr; int _cnt; char*_base; int _flag; int _file; int _charbuf; int _bufsiz; char*_tmpfname;};typedef struct _ibuf FILE//把上述结构体重命名为FILE
不同编译器的FILE类型包含的内容不一定完全相同,但基本都是大同小异,每当打开一个文件时,系统会根据文件的情况自动创建一个FILE结果的变量,并填充其中的信息,我们使用者不必过度关心细节,按周总理说的“求同存异”即可。
一般都是通过一个FILE类型的指针来维护FILE结构的变量,这样使用起来更加方便。
FILE*pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就可以访问该文件。也就是说,通过文件指针变量能找到与它关联的文件如下图所示:
pf是指向文件信息区的,而文件信息区又可以确切的找到与它关联的文件,这样你就可以通过pf找到所需文件并进行相关操作。
文件在读写之前应该先打开文件,在使用后应该关闭文件。
在编写程序时,打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相对于建立了指针和文件的联系。
ANSIC规定使用fopen函数来打开文件,fclose来关闭文件
FILE *fopen(const char* filename, const char* moede);//打开文件//fopen第一个参数是文件名,第二个参数是打开模式//比如你传一个test.txt到一个参数里,传的其实是首字母t的地址//打开模式是这样的,你是想给这个文件写点东西还是想读取这个文件的一些东西int fclose(FILE*stream);//关闭文件
关于打开模式如下图,比如你打开模式是r,那你的打开方式就是读,如果你的文件不存在或者没有被找到,那fopen函数就会调用失败
(图片来自比特就业课,这里只举r一个打开方式,其他打开方式读者可自行对照上表)
fopen打开模式打开data.txt文件会返回一个FILE*的指针,该指针是指向与data.txt文件相关联的文件信息区的起始地址,如果打开失败会返回空指针。
fclose关闭文件相对fopen就简单很多了,你要关闭哪个文件,我们直接传那个文件关联的文件信息区的指针即可,也就是上图的pf
打开和关闭实际操作代码示例如下:
比如我现在要打开E:/c-language-notes/test.21.10.9路径下的data.txt文件
int main(){ //打开文件 //fopen函数 FILE*pf=fopen("E://c-language-notes//test.21.10.9//data.txt", "r"); //这里的"/"可能会与后面的字母构成转义字符,我们用/对/进行转义一下,让/单纯是一个/ //fopen函数会返回一个FILE*的指针,打开失败返回空指针 if (pf == NULL) { perror("fopen");//perror函数显示错误信息 return -1; } //读文件。。。 //关闭文件 fclose(pf); pf = NULL; return 0;}
需要注意的是,fclose关闭文件是不会把pf置为空指针的,我们需要手动操作置为空指针
(图片来自比特就业课)
所有输入流包括:istream 类连续文本模式输入使用、ifstream磁盘文件输入、istringstream 类从内存字符串的输入。
fgetc和fputc函数分别是读入一个字符和输出一个字符
如上图,我们写一个程序时,会产生一些数据,数据会存放在内存中,如果你想把数据写入(输出)到文件里,或者你想把文件里的信息读到内存中,叫读(输入操作)。我们用输入/输出操作就用的是fgetc与fputc
fputc函数示例:
// int fputc(int c, FILE *stream);函数声明int main(){ FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "w");//以“只写w”的模式打开文件 if (pf == NULL) { perror("fopen");//perror函数显示错误信息 return -1; } //写文件 fputc("b", pf); fputc("i", pf); fputc("t", pf); fclose(pf); pf = NULL; return 0;}
运行完上述代码后,相关文件自动出现fputc函数写入的三个字
fgetc函数示例:
//int fgetc(FILE *filename);函数声明int main(){ FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "r");//以“只读r”的模式打开文件 if (pf == NULL) { perror("fopen");//perror函数显示错误信息 return -1; } //读文件 int ch1=fgetc(pf); printf("%c/n", ch1); int ch2 = fgetc(pf); printf("%c/n", ch2); int ch3 = fgetc(pf); printf("%c/n", ch3); fclose(pf); pf = NULL; return 0;}
假设我们现在相关文件里有3个字母abc
我们运行上述程序,程序自动从文件里获取三个字母abc
我们再来看一看文本行输入/输出函数
fputs函数示例:
//int fputs(const char *s, FILE *stream);函数声明//s 代表要输出的字符串的首地址,可以是字符数组名或字符指针变量名。//stream 表示向何种流中输出,可以是标准输出流 stdout,也可以是文件流。标准输出流即屏幕输出,printf 其实也是向标准输出流中输出的。int main(){ FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "w");//以“只写W”的模式打开文件 if (pf == NULL) { perror("fopen");//perror函数显示错误信息 return -1; } //写文件(写一行) fputs("hello world/n", pf); fputs("hello bit/n", pf); fclose(pf); pf = NULL; return 0;}
和fputc差不多,fputc是写一个字母,fputs是写一行,运行完上述程序,相关文件夹出现hello world 和hello bit
fgets函数示例:
//char *fgets(char *buf, int bufsize, FILE *stream);//*buf: 字符型指针,指向用来存储所得数据的地址。//bufsize: 整型数据,指明存储数据的大小。//*stream: 文件结构体指针,将要读取的文件流。int main(){ FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "r");//以“只读r”的模式打开文件 if (pf == NULL) { perror("fopen");//perror函数显示错误信息 return -1; } //读文件(读一行) char arr[20] = { 0 }; fgets(arr,5,pf); printf("%s/n", arr);//打印hell,说是最多读5个其实是读到第四个,然后第五个补/0 fclose(pf); pf = NULL; return 0;}
我们原先文件里有hello world 和hello bit,我们读一行中的5个字符到arr里
说是打印5个字符,其实是打印4个字符,第五个字符是自动补/0
格式化输入输出也就是按某种格式写入或读取
fprintf函数示例:
struct S{ int n; double d;};int main(){ //int fprintf(FILE *filename, const char *string, . . . .);函数声明 //看起来函数声明有点麻烦,我们再来看一下常见的printf函数声明 //int printf( const char *format, … ); //对比一下很容易发现也就是比printf函数多一个指针参数而已,其他的都按printf来即可 struct S s = { 100,3.14 }; FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "w");//以“只写w”的模式打开文件 if (pf == NULL) { perror("fopen");//perror函数显示错误信息 return -1; } //写文件 fprintf(pf,"%d %lf", s.n, s.d); fclose(pf); pf = NULL; return 0;}
fprintf就是printf函数多一个pf指针,其他的和printf都是一样的,上述代码运行一下,相关文件出现100,和3.140000(浮点型默认6位小数)
fscanf函数示例:
struct S{ int n; double d;};int main(){ //int fscanf(FILE *stream, char *format,[argument...]);函数声明 //和fprintf一样,就是scanf函数前面多一个pf指针参数 struct S s = { 0}; FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "r");//以“只读r”的模式打开文件 if (pf == NULL) { perror("fopen");//perror函数显示错误信息 return -1; } //读文件 fscanf(pf,"%d %lf", &(s.n), &(s.d)); printf("%d %lf/n", s.n, s.d); fclose(pf); pf = NULL; return 0;}
原先文件里有100 3.140000,运行程序后读取出这两个数
fwrite函数声明如下
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
ptr-- 这是指向要被写入的元素数组的指针。
size-- 这是要被写入的每个元素的大小,以字节为单位。
nmemb-- 这是元素的个数,每个元素的大小为 size 字节。
stream-- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
fwrite函数示例:
//size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)//ptr-- 这是指向要被写入的元素数组的指针。//size-- 这是要被写入的每个元素的大小,以字节为单位。//nmemb-- 这是元素的个数,每个元素的大小为 size 字节。//stream-- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。struct S{ int n; double d; char name[20];};int main(){ struct S s = { 100,3.14,"zhangsan" }; FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "wb");//以“只写wb”的模式打开文件 if (pf == NULL) { perror("fopen");//perror函数显示错误信息 return -1; } //写文件-二进制方式写 fwrite(&s, sizeof(s), 1, pf); //关闭文件 fclose(pf); pf = NULL; return 0;}
这里要注意的是,我们以二进制fwrite写入,产生的是二进制文件,所以我们以二进制的wb模式打开,文件里的内容如下
因为是二进制文件,我们直接看是看不懂的,但是我们知道可以二进制读,也就是下面的fread函数
函数声明:size_t fread( void *buffer, size_t size, size_t count, FILE *stream )
buffer 是读取的数据存放的内存的指针(可以是数组,也可以是新开辟的空间,buffer就是一个索引)
size 是每次读取的字节数
count 是读取次数
strean 是要读取的文件的指针
fread函数示例:
struct S{ int n; double d; char name[20];};int main(){ struct S s = {0}; FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "rb");//以“只写wb”的模式打开文件 //wb if (pf == NULL) { perror("fopen");//perror函数显示错误信息 return -1; } //读文件-二进制方式读 fread(&s, sizeof(struct S), 1, pf); //打印 printf("%d %lf %s/n", s.n, s.d, s.name); fclose(pf); pf = NULL; return 0;}
我们现在文件里是这些东西,我们运行程序读一下
可以读出之前写入的东西
函数声明:int fseek(FILE *stream, long offset, int fromwhere);
函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。执行成功返回0,否则返回其他数。
fromwhere有三个值:
SEEK_CUR 文件指针当前位置
SEEK_END 文件的末尾
SEEK_SET 文件的起始
比如a的地址就是文件的起始,f就是文件的末尾,假如我现在fgetc读了一个,指针从a往后移动一位,指针指向b,那b所在位置就是SEEK_CUR,也就是文件指针当前位置
注意:随机读写不是乱读,而是想读哪里读哪里,比如我想读文件第三个字母,我就可以直接用随机读写读取第三个字母。我们仍以上面这个文本文档为例:
现在我要读第三个,按照常规的顺序读写,是有一个指针指向a,然后每读一个,指针往后移一位,读第三个要移动2次。fseek函数就是可以快速根据指针的位置和偏移量来定位文件指针,大白话讲就是fseek函数可以快速找到第三个字母的指针。
#define _CRT_SECURE_NO_WARNINGS#include //fseek函数int main(){ //1.打开文件 FILE*pf=fopen("E://c-language-notes//test.21.10.11//data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //2.读文件(随机读写) //读c fseek(pf, 2, SEEK_SET);//现在我要读c,刚开始cur和set都是起始位置,用cur也可 int ch = fgetc(pf); printf("%c/n", ch); //读b fseek(pf, -2, SEEK_CUR); ch = fgetc(pf); printf("%c/n", ch); //3.关闭文件 return 0;}
关于读b,因为我们读c之后,指针会自动往后移一位,所以cur是指向d的,b关于d的偏移量是-2,所以我们用fseek(pf, -2, SEEK_CUR);读取
函数声明:long int ftell(FILE*filenname);
返回文件指针相对起始位置的偏移量
#include //fseek函数int main(){ //1.打开文件 FILE*pf=fopen("E://c-language-notes//test.21.10.11//data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //2.读文件(随机读写) //读c fseek(pf, 2, SEEK_SET);//现在我要读c,刚开始cur和set都是起始位置,用cur也可 int ch = fgetc(pf); printf("%c/n", ch); //读b fseek(pf, -2, SEEK_CUR); ch = fgetc(pf); printf("%c/n", ch); int a=ftell(pf);//b读完之后指针自动往后一位到c,c相对a偏移量为2 printf("%d", a);//打印2 //3.关闭文件 return 0;}
继上一段代码,我们读完b之后指针自动往后移动一位指向c,c相对起始位置a的偏移量为2,所以ftell会返回2
函数声明:void rewind(FILE *stream);
不管pf现在在什么位置,传过去,pf重新指向文件起始位置
根据数据的组织形式,数据文件被称为文本文件或者二进制文件
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前进行转换。以ASCII码的形式存储的文件就是文本文件
一个数据在内存中是怎么存储的呢?
字符一律以ASCII码的形式进行存储,数值型的数据即可用ASCII码的形式存储,也可以用二进制形式存储,如下,我们进行10000的存储
(图片来自比特就业课)
如果我们按ASCII形式存储,把10000共5位,我们把每位上的数字看做一个字符,共要5个字节
如果我们直接按二进制形式进行存储,二进制的10000,是
00000000 00000000 00100111 00010000共需占4个字节(1个整形)
牢记:在文件读取过程中,不能使用feof函数的返回值直接来判断文件的结束与否,而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束
1.文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)
*fgetc判断是否为EOF
fgetc读到一个字符返回int,如果文件结束没读到或者遇到错误,返回EOF
原文件里有abcdef5个字符,现在我们怎么利用fgetc进行打印,并判断是否结束呢?
代码如下:
#include int main(){ //打开文件 FILE*pf=fopen("E://c-language-notes//test.21.10.11//data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; }
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/122562.html
摘要:语言在设计中考虑了函数的高效性和易用性两个原则。在语言中,最常见的当属函数了。以上就是一个函数,它被称为语言的入口函数,或者主函数。例如和都是函数名。形式参数当函数调用完成之后就自动销毁了。 ...
摘要:大家好,我是冰河有句话叫做投资啥都不如投资自己的回报率高。马上就十一国庆假期了,给小伙伴们分享下,从小白程序员到大厂高级技术专家我看过哪些技术类书籍。 大家好,我是...
摘要:时间永远都过得那么快,一晃从年注册,到现在已经过去了年那些被我藏在收藏夹吃灰的文章,已经太多了,是时候把他们整理一下了。那是因为收藏夹太乱,橡皮擦给设置私密了,不收拾不好看呀。 ...
摘要:层确保数据一致性和可靠性。保证集群的相关组件在同一时刻能够达成一致,相当于集群的领导层,负责收集更新和发布集群信息。元数据服务器,跟踪文件层次结构并存储只供使用的元数据。启迪云-高级开发工程师 侯玉彬前言上一次简单的介绍Ceph的过去和未来的发展。这一节将详细介绍Ceph的构件以及组件。Ceph存储架构Ceph 存储集群由几个不同的daemon组成,每个daemon负责Ceph 的一个独特...
阅读 3193·2023-04-26 02:27
阅读 2033·2021-11-22 14:44
阅读 3963·2021-10-22 09:54
阅读 3052·2021-10-14 09:43
阅读 637·2021-09-23 11:53
阅读 12323·2021-09-22 15:33
阅读 2587·2019-08-30 15:54
阅读 2549·2019-08-30 14:04