所谓文件就是我们字面理解的意思,存储数据的载体,比如电脑磁盘上就有大量文件
文件一般分为两类:
1.程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
2.数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
3.数据文件又分为文本文件或二进制文件
二进制文件
数据在内存中以二进制的形式存储,如果不加转换的输出到外存
文本文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件
简单来说文本文件存储的是字符(与ASCLL码一一对应),二进制文件存储的是二进制数(010101…)
如果我们用文件操作向文件写入数据是不是要选择以哪种方式进行写入
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
举例
若存储一个整形 int a=10000;
如果是二进制写入直接把10000这个十进制数转化为二进制数存进去就好了
如果是以文本写入由于文本存储的是字符,所以将10000转化为5个字符就好了拆分为’1’ ‘0’ ‘0’ ‘0’ ‘0’
但字符存进去其是存储的是ASCLL值,则最终存储进去的是 字符转化为ASCLL值 49 48 48 48 48
看图就懂了
接下来就正式开始要想读写文件肯定得先打开文件,既然有打开肯定就有关闭咯
1.文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”
2.文件类型
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位等)。这些信息是保存在一个结构体变量中的。这个结构体类型就是所谓的文件类型 又给它取个别名叫FILE(文件类型)
FILE就是结构体类型声明
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量
,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
FILE *pf:pf就是一个文件指针的变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件
。
在讲文件操作函数之前先了解一下几个概念
stream
流主要是指一种逻辑上的概念,它提供或存储数据。产生数据的叫输入流,消耗数据的叫输出流。至于怎么产生,又怎么消耗,这是一种物理上的实现,根据每种设备有所不同,但C语言中对它们一视同仁,以一个“流”字来概括它们的特征。作为流的使用者来说,不需要关心太多的细节。
C语言中对流除了分为I/O流之外,还分为文本流与二进制流。
程序运行时默认打开了三个流
stdin
stdout
stderr
键盘–标准输入设备–stdin
屏幕–标准输出设备–stdout
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件
,也相当于建立了指针和文件的关系。ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。
打开文件时要选择一种打开方式
注意:上面指定文件不存在时就建立一个新文件的打开方式,如果指定文件存在还是会建立一个新文件覆盖原有的文件
分为顺序读写(从前往后读写)和随机读写(指定位置读写)
读写时会自动生成一个文件指针来确定读写的位置
,但默认是指向首元素,后面介绍的随机读写函数就可以控制文件指针的偏移量(相对于首元素)来确定读写文件的那个位置
接下来一 一介绍函数
参数
character:要写入的字符
FILE *stream:写到哪个文件流或其他输出流 也可以是屏幕(-标准输出设备) 输出流的FILE的指针
直接上代码
第一个代码附上详细注解,后面我就不赘述了
//向文件顺序写入字符
int main()
{
FILE * pfwrite=fopen("test.txt","w");//以写的形式打开test.txt文本文件,并返回一个FILE* 的指针\
这里用一个pfwrite 的FILE* 的指针变量来接收,pfwrite就管理test.txt这个文件
if ( pfwrite ==NULL )//打开文件失败返回一个NULL
{
printf("%s",strerror(errno));//打印错误信息
return 0;//提前嗝屁
}
//写入字符
fputc(97,pfwrite);//以ASCLL形式写入,以字符形式显示 'a'
fputc('a',pfwrite);//跟上面的等价
fputc('n',pfwrite);
fclose(pfwrite);//关闭文件
pfwrite=NULL;//防止变成野指针
return 0;
}
运行代码之后在代码路径找到我们写入数据的文件test.c打开发现数据写入成功
其实fputc函数与putc的参数基本一样puts函数也可以向文件输出字符
,但一般用fputc它带来一个前缀f表示一般是文件输出其他并没有啥区别
将函数的第二个参数改成标准输出流就OK啦
//向屏幕写入(输出)字符
int main()
{
//写入字符
fputc('p',stdout);//stdout-标准输出流-也就是输出到屏幕
fputc('i',stdout);
fputc('n',stdout);
return 0;
}
参数
1.stream:从哪个文件流或其他输入流, 输入流的FILE的指针
2.返回值:读取的字符
这里解释一下返回的既然是一个字符为啥用int接收,上文也说到写字符写进内存是以ASCLL码值写入的,其实说字符本质上就是十进制的ASCLL码值
也不为过
接下来我们要讲上面利用fgetc函数写入文件的数据再用fputc函数从文件读出,并利用printf函数输出到屏幕
//向文件顺序读取字符打印到屏幕
int main()
{
FILE * pfread=fopen("test.txt","r");//以读的形式打开文件一个文本文件
if ( pfread ==NULL )
{
printf("%s",strerror(errno));//打开失败输出错误码
return 0;
}
//向文件读取字符
printf("%c", fgetc(pfread));//向test.txt文件读取一个字符
printf("%c", fgetc(pfread));
printf("%c", fgetc(pfread));
fclose(pfread); //关闭文件
pfread=NULL;//防止变成野指针
return 0;
}
参数
1.str:要写入的字符串首地址
2.stream:写到哪个文件流或其他输出流 也可以是
屏幕(-标准输出设备) 输出流的FILE的指针
//向文件写入一个字符串
int main()
{
char buf[10]="acbdef";
FILE*pfwrite=fopen("test.txt","w");//以写的形式打开文件一个文本文件
if ( pfwrite== NULL)
{
printf("%s",strerror(errno));
return 0;
}
fputs(buf,pfwrite);//将buf字符串写入文件test.txt
fclose(pfwrite); //关闭文件
pfwrite=NULL;//防止变成野指针
return 0;
}
参数
1.str:存储读取的字符串
2.num:最多读取多少个字符
3.stream:向到哪个文件流或其他输入流 也可以是键盘stdin(-标准输入设备读取一个字符串) 输入流的FILE的指针
4.返回值:如果读取成功返回str,读取失败返回NULL
向刚利用fputs函数写入一个字符串的文件又读出来输出到屏幕
int main()
{
char buf[1024];
FILE*pfread=fopen("test.txt","r");//以读的形式打开文件一个文本文件
if ( pfread== NULL)
{
printf("%s",strerror(errno));
return 0;
}
fgets(buf,1024,pfread);//向文件读取一个字符串
printf("%s",buf);
fclose(pfread); //关闭文件
pfread=NULL;//防止变成野指针
return 0;
}
输出结果
接下来接着利用fgets函数从键盘(标准输入流—stdin)读取一个字符串, 再将读取的字符串输出到屏幕(标准输出流—stdout)
直接上代码
int main()
{
char buf[1024]={0};
fgets(buf,1024,stdin);//从键盘读取一行字符最多读取1024个字符,存储到buf数组
fputs(buf,stdout);//将数组buf中的字符输出到屏幕
return 0;
}
显示结果
第一行是键盘输入字符串通过fgets函数存入buf数组
第二行是通过fguts函数将buf数组里字符串输出到屏幕
接下来的fprintf 函数 和 fscanf函数通过与传统的我们熟悉的printf函数与scanf函数对比一下就完全明白了
直接看图就明白了,总的一句话适用于所有输出流的函数既可以输出到文件也可以输出到屏幕
,输入是同理,输入针对与文件和键盘
接下来利用fprintf向文件流分格式化的输出数据
//随便定义一个结构体类型
struct S
{
int n;
float f;
char arr[5];
};
//格式化的输入输出
int main()
{
struct S s={10, 3.14f, "min"};
FILE *pfwrite= fopen("test.txt","w");//以写的形式打开一个文本文件
if ( pfwrite == NULL)
{
printf("%s",strerror(errno));
}
//%0.2f保留两位小数输出
fprintf(pfwrite, "%d %0.2f %s ",s.n, s.f, s.arr);//选择输出的流这里是输出(写入)到文件,将数据格式化输出
fclose(pfwrite ); //关闭文件
pfwrite =NULL;//防止变成野指针
return 0;
}
这里就将fprintf写入文件的格式化数据读出,然后利用printf函数打印到屏幕
//随便定义一个结构体类型
struct S
{
int n;
float f;
char arr[5];
};
//格式化的输入
int main()
{
struct S s={0};//创建一个结构体变量来存储fscanf读取的格式化数据
FILE *pfread= fopen("test.txt","r");//以读的形式打开一个文本文件
if ( pfread == NULL)
{
printf("%s",strerror(errno));
}
fscanf(pfread, "%d %f %s ",&(s.n),& (s.f), s.arr);//选择那个流读数据 将数据格式化输入
printf("%d %0.2f %s ",s.n, s.f, s.arr);
fclose(pfread); //关闭文件
pfread=NULL;//防止变成野指针
return 0;
}
读取结果
用fscanf从键盘读一个字符串,用fprintf输出到屏幕
//从键盘读,从屏幕输出
int main()
{
char str[10]={0};
fscanf(stdin,"%s",str);
fprintf(stdout,"%s ",str);
return 0;
}
这里的数据块 可以是一个数组,也可以一个元素
参数
ptr:要被写入的内存指针
size:写入每个元素的大小,单位为字节
count:最多写入元素个数
stream:指向指定输出流的FIEL对象的指针,一般为文件流
返回值:写入成功的元素总数
向以二进制的形式将数据写入文件
int main()
{
int n =8;
FILE *pfwrite= fopen("test.txt","wb");//以写入形式打开一个二进制文件
if ( pfwrite == NULL)
{
printf("%s",strerror(errno));
}
fwrite(&n,sizeof( int ),1,pfwrite);
fclose(pfwrite); //关闭文件
pfwrite=NULL;//防止变成野指针
return 0;
}
接下来打开文件看看
发现并不能看懂,因为我们以二进制形式写入的,我们以二进制文件打开,打开步骤如图
1.用vs添加该文件
2.以二进制编译器打开
3.这样就显示我们二进制存储的数据啦
fread的参数与fwrite的参数几一样,只是一个是向文件写数据,一个是向文件读数据,但他们都是以二进制方式读写
参数
ptr:存储数据的内存指针
size:读出每个元素的大小,单位为字节
count:最多读取元素个数
stream:指向指定输入流的FIEL对象的指针,一般为文件流
返回值:实际读到的元素个数,若出现错误或到达文件末尾则可能小于count
就将fwrite以二进制写入文件的数据,用fread函数以二进制读出并打印
//以二进制方式读取文件数据
int main()
{
int tmp=0;//建立一个临时变量来存储读取数据
FILE *pfread= fopen("test.txt","rb");//以读的形式打开一个二进制文件
if ( pfread == NULL)
{
printf("%s",strerror(errno));
}
fread(&tmp,sizeof( int ),1,pfread);
printf("%d",tmp);
fclose(pfread); //关闭文件
pfread=NULL;//防止变成野指针
return 0;
}
前面的函数全是只能顺序读取文件的数据
,打开文件读取数据时文件会有一个文件指针指向文件的起始位置,读取完一个元素,指针会自动指向下一个元素。
参数
offset:相对于当前指针的 指针偏移量
origin:文件指针的当前位置有三个选择(宏)
1.SEEK_CUR 文件指针的当前位置
2.SEEK_END 文件的末尾位置
3.SEEK_SET 文件的起始位置
4.stream 指向标识流的FILE对象的指针。
向要实现随机读取数据,例如我一上来就读取 d 这个元素只能让指针偏移到指向 d 这个元素然后读取,如果选择文件起始位置开始是不是要让指针偏移 3 就指向了 d 元素
/文件指针偏移
int main()
{
char ch=0;
FILE *pfread= fopen("test.txt","r");//以读的形式打开一个文本文件里面已经存储了abcdef
if ( pfread == NULL)
{
printf("%s",strerror(errno));
}
fseek(pfread,3,SEEK_SET);//文件指针指针起始位置,指针偏移3个字节指向 d元素
ch =fgetc(pfread);//读取当前文件指针的位置的元素
printf("%c\n",ch);//打印
fclose(pfread); //关闭文件
pfread=NULL;//防止变成野指针
return 0;
}
参数
1.stream :指向标识流的FILE对象的指针
2.返回值:返回文件指针相对于起始位置的偏移量
详情看注释
//返回文件指针相对于起始位置的偏移量
int main()
{
char ch=0;
FILE *pfread= fopen("test.txt","r");//以读的形式打开一个文本文件里面已经存储了abcdef
if ( pfread == NULL)
{
printf("%s",strerror(errno));
}
ch =fgetc(pfread);//读取一个元素后,文件指针自动后移一位 所以偏移量为1
printf("%c\n",ch);
printf("偏移量为:%d\n ", ftell(pfread));
fclose(pfread); //关闭文件
pfread=NULL;//防止变成野指针
return 0;
}
五.文件结束的判定
文件末尾会放一个文件结束标志 EOF(宏)真实的值为 -1
函数返回值详情参考上方函数介绍
一.文本文件读取是否结束,
1.fgetc函数 判断返回值是否为 EOF(-1)
2.fgets函数 返回一个 NULL
二.二进制文件的读取结束判断
1.fread 函数判断返回值是否小于实际要读的个数。
(我在通讯录中就利用了检测fread的返回值从而判定文件里的联系人信息是否读完,详情看我通讯录通讯录)
但feof 与ferror函数配合一般用于判定文件是读取失败还是读取结束
如果是返回非0整数,否则返回0 (通俗来讲就是文件读取失败返回 非0 ,否则返回0)
feof的工作原理为查看当前文件指针 指向的内容有没有字符
若有返回 0,如果没有返回 1,这里会有一个误区一个空的文件它文件的指针指向的内容是有 EOF 字符的
,这样空文件的直接用feof直接判定的话会一直返回 0 也就是不是空文件显然不然这样直接进行判定
直接上代码理解详情看注释
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if ( fp == NULL)
{
printf("%s",strerror(errno));
}
//**fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF**
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))//若为 1 则文件出现错误
{
puts("I/O error when reading");
}
else if (feof(fp))
{
//如果代码执行到这,因上面那个读取循环已经把文件指针偏移到 指向 EOF(文件结束标志)的后面则后面已经没有字符所以feof函数会返回 1 则文件正常读取完毕
puts("End of file reached successfully");
}
fclose(fp); //关闭文件
fp=NULL;//防止变成野指针
return 0;
}
到这里文件操作的重要函数基本全部讲完,我也花了两天时间进行整理,实属不易,由于篇幅太长,若有错误的地方欢迎指正 本文有些资料参考比特鹏哥 嘿嘿
看到这的就关注一下叭,已经更新《单片机入门必看》!!!!