目录
3- fprintf 、fscanf (gets不安全,用fgets)
a- fseek、ftell(long范围内操作,常操作二进制文件!)
写在前面
之前从单片机起步,没用过文件操作,甚至看C语言的书,感觉文件操作好高深,对其望而生畏。直到接触到linux,尤其是学习了字符设备驱动,发现字符设备就是对文件的操作。都说linux下一切皆文件,对其理解还不是太深,那就满满学习吧。
学习目标:
掌握C语言中的文件操作,包括:打开、关闭;读写;定位等。
学习总结:
1- 文件打开、关闭 fopen;fclose,及其返回值。fopen正常返回文件指针,异常返回NULL;fclose正常返回0,异常返回EOF。注意以w/w+模式打开文件,会冲掉文件原有内容,另外打开模式用双引号,不是单引号!!
FILE * fp = fopen("文件", "mode");
fclose(fp);
2- getc、putc。getc注意循环输入吃掉回车,注意putc不自动添加回车。
循环输入条件:while( (ch = getc(stdin)) != '\n')
ch = getc(fp);
putc(ch,fp);
3- fprintf、fscanf用法和printf、scanf相似。用于向文件(显示器)写东西、从文件(键盘)读出数据。注意gets、puts用法不安全,用fputs、fgets代替。
fprintf(fd, "%s", buff);
fscanf(fd, "%s", buff);
4- fgets、fputs用于从文件(stdin)读数据、输出到stdout。fgets比gets安全,不要用gets,因为fgets完全可以替代gets。
fgets(buff, len, fp);
fputs(buff, fp);
注意事项:
fgets会接收回车'\n',且当输入小于len,会在输入后面加上字符串结尾'\0',并且如果加上'\0'后还没到len,会加上换行符'\n'。
如果超过了len,则最多接受len-1个字符,因为还要加上'\0',这时候就没法在'\0'后面加'\n'了。也就是fputs没有换行的效果了。
所以为了有换行效果,输入最大len-2个字符。循环输入另说。
循环输入条件:while( (fgets(buf,10,stdin) != NULL ) && (buf[0] != '\n'))
如果输入超过len个字符仍会打印,并且有换行效果。是由于fgets一次最多读入len-1个字符,并在其结尾加入'\0'(不加'\n'!!!),并将其放到输出缓存中,当读完所有一行数据一块输出。
使用fgets输入文件名时,文件名中包含'\n',需要处理掉,不然会影响strcmp比较,这里涉及到strcmp、strchr字符串知识点。
用法:
a-fgets从stdin/文件1 读入数据
b- fprintf将数据写入文件2
c- fscanf从文件2读出数据
d- fputs将数据刷新到屏幕
5- 了解文件指针偏移设置。fseek、ftell;fsetpos、fgetpos。如果使用后者操作偏移量使用fpos_t.__pos=偏移量。
fread;fwrite;fseek;ftell;fgetpos;fsetpos;fflush等
6- fread fwrite重点
/*返回读出的个数。将读入的数据写到buf中
示例:
以char为单位,读15个char数据
size_t cnt = fread(buf,sizeof(char),15,fp);
*/
size_t fread(buf,读单位,读多少个,FILE *fp);
/*写,将buf中数据写到fp中*/
size_t fwrite(buf,写单位,写多少个,FILE *fp);
rewind将文件指针置为开头
rewind(fp);
exit和return区别
正文:
1- 文件打开、关闭
fopen、fclose对应于文件打开关闭,
语法:
fopen(文件名,打开模式);//打开成功返回文件指针FILE *; 打开失败返回NULL
fclose(文件句柄);//文件关闭成功返回0; 关闭失败返回EOF
在这里设计到文件指针的概念,C语言中该变量为FILE *数据类型,我们对于文件的操作都是通过文件指针进行的。对其暂时不要过多探究,只需知道我们操作文件必须借助文件指针即可。
介绍三个便文件指针:
stdin | 标准输入,比如从键盘读取 |
stdout | 标准输出,比如输出到显示器 |
stderr | 标准错误输出,比如输出到显示器 |
对于文本文件打开模式主要有r、w、a三种,衍生出r+、w+、a+。对于二进制文件在此基础上加上b参数即可。对于文本文件和二进制文件区别,暂时不纠结。
注意打开模式、文件名用双引号,双引号!!!!
r | 打开一个已有的文本文件,允许读取文件。 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。简而言之就是,文件不存在创建,不论文件里有没有数据我都清空,重写。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
rb; wb; ab; ab+; a+b; wb+;w+b; ab+;a+b | 这里的几种模式是针对二进制模式文件操作的模式 |
先上个小例子来看下,这两个函数怎么用,简单地打开一个文件,然后关闭。通过下面的例子,对文件的基本操作应该有了基本的认识了吧,let's go,继续往下搞。
解读写exit()函数,表示退出程序,和return不同。加入我在递归调用中调用exit函数,程序会终止整个程序;如果使用return,只会终止本次调用。
#include<stdio.h>
#include<stdlib.h> //exit()
int main()
{
FILE *fp;
fp = fopen("tmp.txt","r");
if(NULL == fp)
{
printf("open file fail\n");
exit(1);
}
printf("open file succ\n");
if(EOF == fclose(fp))
{
printf("close file fail\n");
exit(1);
}
printf("close file succ\n");
return 0;
}
2- getc、putc
和getchar、putchar类似,好像这两个用的不多,了解即可。一次只能接受一个字符
语法:
接收读入的字符 = getc(文件指针); //从文件指针读出1个字符
putc(文件指针); //向文件指针写一个字符
举例:
A- 从键盘输入一个字符,并将其输出到屏幕。
注意:
I- getc()循环入口处理回车。getc()一次只能读一个字符,假如我们输入a[回车],程序会先将a读入,打印,第二次读入回车,如果不在while中加入getc吃掉回车,下次在while循环入口检测到回车就会停止循环!
II- putc()不自动加回车。
#include<stdio.h>
#include<stdlib.h> //exit()
int main()
{
char ch;
printf("please enter:");
while( (ch = getc(stdin)) != '\n')
{
printf("printf char:");
putc(ch,stdout);
getc(stdin); //吃掉回车
printf("\n");
printf("please enter:");
}
return 0;
}
B- 从文件读并输出
注意:应该先读一个字符看是否是文件结尾EOF。另外文件打开模式不可以用w/w+,因为会冲掉文件原有内容。
#include<stdio.h>
#include<stdlib.h> //exit()
int main()
{
FILE * fp;
char ch;
fp = fopen("tmp.txt","a+");
if(fp == NULL)
{
printf("open file fail\n");
exit(1);
}
while( (ch = getc(fp)) != EOF )
{
putc(ch, stdout);
}
if(EOF == fclose(fp))
{
printf("close file fail\n");
exit(1);
}
return 0;
}
C- 文件备份
打开一个文件,将其内容复制到另一个文件,另一个文件名在第一个文件名基础上加上_bk。
写完这个程序,有木有感觉和前面的不一样,文件操作好像是那么回事了。好像抓住了文件操作的命脉,有木有。
哈哈哈哈,这样就是个简易的文件备份程序。将它复杂话就是真正的文件备份了。
#include<stdio.h>
#include<stdlib.h> //exit()
#include<string.h> //字符串头文件
int main()
{
FILE * fp_src,*fp_bk;
char ch;
char name[10];
/*打开源文件,打开模式可以用r,因为我是边调试边写的,所以用了a+*/
fp_src = fopen("tmp","a+");
if(fp_src == NULL)
{
printf("open src_file fail\n");
exit(1);
}
/*构造备份文件名. 10-4是追加_bk,并余留一个字符*/
strncpy(name,"tmp",10-4);
strcat(name,"_bk");
//puts(name);
/*打开备份文件*/
fp_bk = fopen(name,"w");
if(fp_bk == NULL)
{
printf("open bk_file fail\n");
exit(1);
}
/*将源文件数据拷贝到备份文件*/
while( (ch = getc(fp_src)) != EOF )
{
putc(ch,fp_bk);
}
if((EOF == fclose(fp_src)) || (EOF == fclose(fp_bk)))
{
printf("close file fail\n");
exit(1);
}
return 0;
}
3- fprintf 、fscanf (gets不安全,用fgets)
用法同printf、scanf,一个是操作屏幕、键盘,另一个是操作文件,当然fprintf、fscanf也可以操作屏幕、键盘
fprintf写入正确,返回值为写入的个数,否则返回负值。
fscanf读入成功,返回读出的个数;如果到达文件结尾或读错误,返回EOF。
语法:
/*printf将数据写到屏幕,fprintf将数据写到文件*/
fprintf(待写入文件指针,格式字符串);
/*scanf从键盘读出数据,fscanf从文件读出数据*/
fscanf(读出数据的文件指针,格式字符串);
示例:
功能:从键盘读入字符串输入到文件,并打印出来
注意使用gets,在ubuntu环境下会有编译警告,查了下其他博客资料,说是gets不安全,最好用fgets代替。
参考:https://blog.csdn.net/weixin_34336526/article/details/89696487
#include<stdio.h>
#include<stdlib.h> //exit()
#include<string.h> //字符串头文件
int main()
{
FILE * fp;
char words[100];
/*打开文件*/
fp = fopen("tmp","w+");
if( NULL == fp)
{
fprintf(stdout,"open file fail\n");
exit(1);
}
printf("now begain to write file\n");
printf("please enter:\n");
/*将输入的字符串写入文件
*循环入口条件,输入非空,gets丢弃回车符'\n'
*/
while( (gets(words) != NULL ) && (words[0] != '\0'))
{
fprintf(fp,"%s", words);
}
fprintf(fp,"\n");
/*将文件指针定位到开头*/
rewind(fp);
/*读出文件*/
while(fscanf(fp,"%s",words) != EOF)
puts(words);
if( EOF == fclose(fp) )
{
printf("close file fail\n");
exit(1);
}
return 0;
}
4- fgets、gputs
用法和gets、puts安全,为何安全先不管。。。
语法:
/*文件指针可以是stdin*/
fgets(获取数据的文件指针, 最大获取的字符个数, 输出字符的容器);
/*文件指针可以是stdout*/
fputs(写入的数据 ,写入数据的文件指针);
需要注意的是:
a- fgets获取字符时,遇到回车符不丢弃;gets丢弃回车符'\n',所以3-、4-的循环入口判断条件分别为'\0'和'\n'
b- fputs打印时候不打印回车;puts打印回车。
示例:
A- 将3-示例中gets、puts用fgets、fputs代替
#include<stdio.h>
#include<stdlib.h> //exit()
#include<string.h> //字符串头文件
int main()
{
FILE * fp;
char words[100];
/*打开文件*/
fp = fopen("tmp","w+");
if( NULL == fp)
{
fprintf(stdout,"open file fail\n");
exit(1);
}
printf("now begain to write file\n");
printf("please enter:\n");
/*将输入的字符串写入文件
*循环入口条件,输入非空
*/
//while( (gets(words) != NULL ) && (words[0] != '\0'))
while((fgets(words,100,stdin) != NULL) && (words[0] != '\n'))
{
fprintf(fp,"%s", words);
}
fprintf(fp,"\n");
/*将文件指针定位到开头*/
rewind(fp);
/*读出文件*/
while(fscanf(fp,"%s",words) != EOF)
{
fputs(words,stdout);
fputs("\n",stdout);
}
/*关闭文件*/
if( EOF == fclose(fp) )
{
printf("close file fail\n");
exit(1);
}
return 0;
}
B- fgets、fputs注意说明
知识点:
fgets会接收回车'\n',且当输入小于len,会在输入后面加上字符串结尾'\0',并且如果加上'\0'后还没到len,会加上换行符'\n'。
如果超过了len,则最多接受len-1个字符,因为还要加上'\0',这时候就没法在'\0'后面加'\n'了。也就是fputs没有换行的效果了。
所以为了有换行效果,输入最大len-2个字符。循环输入另说。
I- 可以用下面代码做实验:
#include<stdio.h>
#include<stdlib.h> //exit()
#include<string.h> //字符串头文件
int main()
{
char buf[10];
/*输入小于等于8个有换行效果,否则无换行效果*/
fgets(buf,10,stdin);
fputs(buf,stdout);
return 0;
}
II- 循环输入
注意:此时如果输入超过10个字符仍会打印,并且有换行效果。是由于fgets一次最多读入9个字符,并在其结尾加入'\0'(不加'\n'!!!),并将其放到输出缓存中,当读完所有一行数据一块输出。
#include<stdio.h>
#include<stdlib.h> //exit()
#include<string.h> //字符串头文件
int main()
{
char buf[10];
while( (fgets(buf,10,stdin) != NULL ) && (buf[0] != '\n'))
fputs(buf,stdout);
return 0;
}
5- 文件光标操作
包括fseek、ftell;fsetpos、fgetpos
a- fseek、ftell(long范围内操作,常操作二进制文件!)
fseek用于将文件指针定位到文件某个位置
ftell用于返回文件指针位置到文件开头的距离
二者配合使用,用于在long数据范围内操作文件。
语法:
/*偏移量为long型,常传入0L、1L、-1L等数据*/
fseek(fp, 偏移, 模式);
/*返回的距离为long型*/
距离 = ftell(fp);
fseek的偏移模式有:
SEEK_SET | 文件开始位置 |
SEEK_CUR | 文件当前位置 |
SEEK_END | 文件结尾位置 |
形如:fseek表示将文件指针偏移到文件末尾,ftell返回文件当前位置到文件开头位置的距离
fseek(fp, 0L, SEEK_END);
dist = ftell(fp);
示例:
打开一个文件,以文件末尾向文件开头的方向,将字符输出。
这里操作的二进制文件,ab+
#include<stdio.h>
#include<stdlib.h> //exit()
#include<string.h> //字符串头文件
int main()
{
FILE * fp;
char ch;
long count,last;
/*打开文件*/
fp = fopen("tmp","ab+");
if( NULL == fp)
{
fprintf(stdout,"open file fail\n");
exit(1);
}
/*定位到文件结尾*/
fseek(fp, 0L, SEEK_END);
/*计算文件开头到结尾的距离*/
last = ftell(fp);
for(count=1L; count <= last; count++)
{
fseek(fp, -count,SEEK_END);//由于文件最后一个为EOF,所以先移到倒数第1个字符
ch = getc(fp);
if(ch != '\n')
putc(ch,stdout);
}
printf("\n");
/*关闭文件*/
if( EOF == fclose(fp) )
{
printf("close file fail\n");
exit(1);
}
return 0;
}
b- fsetpos、fgetpos(fpos_t范围内操作)
fpos_t为新的数据类型
语法
/*设置文件指针位置*/
fsetpos(fp, fpos_t *pos);
/*获取文件指针位置*/
fgetpos(fp, fpos_t *pos);
示例:
打开一个文件,输入字符,然后文件头将字符输出
操作文件偏移可以用如下赋值,注意是两个下划线:
pos.__pos = 偏移量;
#include<stdio.h>
#include<stdlib.h> //exit()
#include<string.h> //字符串头文件
int main()
{
FILE * fp;
char buf[10];
fpos_t pos;
/*打开文件*/
fp = fopen("tmp","wb+");
if( NULL == fp)
{
fprintf(stdout,"open file fail\n");
exit(1);
}
/*暂存文件开头位置*/
fgetpos(fp,&pos);
while( (fgets(buf,10,stdin) != NULL) && (buf[0] != '\n') )
fprintf(fp,"%s",buf);
//pos.__pos = 5;
fsetpos(fp, &pos);
while( fscanf(fp,"%s",buf) != EOF)
fputs(buf,stdout);
/*关闭文件*/
if( EOF == fclose(fp) )
{
printf("close file fail\n");
exit(1);
}
return 0;
}
6- fread fwrite
语法:
/*返回读出的个数。将读入的数据写到buf中
示例:
以char为单位,读15个char数据
size_t cnt = fread(buf,sizeof(char),15,fp);
*/
size_t fread(buf,读单位,读多少个,FILE *fp);
/*写,将buf中数据写到fp中*/
size_t fwrite(buf,写单位,写多少个,FILE *fp);
示例:
从源文件中读数据,将其追加写到tmp_dest文件中。
特别注意:使用fgets输入文件名时,文件名中包含'\n',需要处理掉,不然会影响strcmp比较,这里涉及到strcmp、strchr字符串知识点。
这里使用了setvbuf()、feof()、ferror(),详细解读见其他。
#include<stdio.h>
#include<stdlib.h> //exit()
#include<string.h> //字符串头文件
int main()
{
FILE *fp_dest,*fp_src;
int ret;
char *pos;
char buf_r[1024];
char buf_w[1024];
char name_src[10];
char tmp[1024];
size_t cnt;
/*初始化缓存,注意是单引号。。。*/
memset(buf_r,'\0',sizeof(buf_r));
memset(buf_w,'\0',sizeof(buf_w));
/*打开待写入文件fp_dest,并设置缓存为buf*/
fp_dest = fopen("tmp_dest","a");
if(NULL == fp_dest)
{
fprintf(stderr,"open file:%s fail\n","tmp_dest");
exit(1);
}
ret = setvbuf(fp_dest,buf_r,_IOFBF,1024);
if(ret != 0)
{
fprintf(stderr,"setvbuf err\n");
exit(1);
}
/*开始操作*/
fputs("enter name_src of to be read:",stdout);
while((fgets(name_src,10,stdin) != NULL) && (name_src[0] != '\n') )
{
/*处理掉fgets末尾的‘\n'*/
pos = strchr(name_src,'\n');
if(pos)
*pos = '\0';
/*如果文件名一样,不做操作,进行下一次循环*/
if(strcmp(name_src,"tmp_dest") == 0)
{
fprintf(stderr,"dest_file:%s is same as src_file:%s \n","tmp_dest",name_src);
}
else if((fp_src=fopen(name_src,"r")) == NULL) //打开文件失败
{
fprintf(stderr,"open file:%s fail\n",name_src);
}
else //开始文件读写
{
/*设置输出缓存*/
ret = setvbuf(fp_src,buf_w,_IOFBF,1024);
if(ret != 0)
{
fprintf(stderr,"setvbuf err\n");
exit(1);
}
/*将fp_src中内容追加到fp_dest*/
while((cnt = fread(tmp,sizeof(char),1024,fp_src)) > 0 )
fwrite(tmp,sizeof(char),cnt,fp_dest);
/*校验读写是否有误*/
if(ferror(fp_dest) != 0)
fprintf(stderr,"write fail\n");
if(ferror(fp_src) != 0)
fprintf(stderr,"read fail\n");
fclose(fp_src);
fputs("enter next file name to be read:",stdout);
}
}
/*关闭文件*/
if(EOF == fclose(fp_dest))
{
fprintf(stderr,"close file:%s fail","tmp_dest");
exit(1);
}
return 0;
}
7- 其他(了解)
a- setvbuf
设置缓存。比如我们输出到屏幕时,会将其先输出到缓存,然后才输出到屏幕,这里为文件指针指定缓存。但实际作用还没感受到,了解。
/*这里模式主要由
_IOFBF:满缓存
_IOLBF:行缓存
_IONBF:无缓存*/
setvbuf( 需要缓存的文件指针,缓存容器, 模式,大小);
参考:https://www.runoob.com/cprogramming/c-function-setvbuf.html
b- fflush
将未输出的缓存强制输出
fflush(FILE *fp);
printf为行缓存,有‘\n’强制输出。如果不加换行,可使用fflush(stdout)强制输出
有些场景需要强制刷新缓存:https://blog.csdn.net/pfl_327/article/details/78903110
c- feof
检查是否到文件结尾,到结尾返回非0值
int feof(FILE *fp);
d- ferror
检查读写是否有误,有误返回非0值
int feof(FILE *fp);
e- ungetc
将字符放回到输入流。
int ungetc(int c, FILE *fp);
比如用于文件中字符替换。打开一个文件,读到是'a'则将其替换为'b',否则不替换。
参考:https://www.runoob.com/cprogramming/c-function-ungetc.html