在了解C语言文件操作时,首先我们应该知道什么是文件?需要一点文件的基础知识。
目录
文件知识储备
文件是可以持久化保存数据,一般磁盘上的文件就是文件。在程序设计中,以功能分类文件一般分为两种:程序文件,数据文件。
程序⽂件
程序⽂件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows 环境后缀为.exe)。
int main()
{
FILE* pf = fopen("test.txt", "wb");//wr以二进制文本写入
if (pf == NULL)
{
perror("fopen");//报错,也可以用assert
return 1;//提前结束程序
}
int a = 10000;
fwrite(&a, 4, 1, pf);
fclose(pf);//关闭文件,节省资源
pf = NULL;//赋空指针无意解引用
return 0;
}
以二进制编辑器打开test.txt,可以看到它的十六进制,查看是不要关闭文件(屏蔽fclose(pf))
流的概念
文件指针
//VS2013编译环境提供的 stdio.h 头⽂件中有以下的⽂件类型申明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
⽂件的打开和关闭
⽂件使⽤⽅式 | 含义 | 如果指定⽂件不存在 |
“r”(只读) | 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 | 出错 |
“w”(只写) | 为了输出数据打开一个文本文件 | 建⽴⼀个新的⽂件 |
a”(追加) | 向⽂本⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
“rb”(只读) | 为了输⼊数据,打开⼀个二进制文件 | 出错 |
“wb”(只读) | 为了输⼊数据,打开⼀个二进制文件 | 建⽴⼀个新的⽂件 |
“ab”(追加) | 向一个二进制⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
“r+”(读写) | 为了读和写,打开⼀个⽂本文件 | 出错 |
“w+”(读写) | 为了读和写,建立⼀个⽂本文件 | 建⽴⼀个新的⽂件 |
“a+”(读写) | 打开⼀个⽂件,在⽂件尾进⾏读写 建⽴⼀个新的⽂件 | 建⽴⼀个新的⽂件 |
“wb+”(读写) | 为了读和写,新建⼀个新的⼆进制⽂件 | 建⽴⼀个新的⽂件 |
“rb+”(读写) | 为了读和写打开⼀个⼆进制⽂件 | 出错 |
“ab+”(读写) | 打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写 | 建立⼀个新的⽂件 |
⽂件的顺序读写
文件读写应该是要有一定的规则的
函数名 | 功能 | 适⽤于 |
fgetc | 字符输⼊函数 | 所有输⼊流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | ⽂本⾏输⼊函数 | 所有输⼊流 |
fputs | ⽂本⾏输出函数 | 所有输出流 |
fscanf | 格式化输⼊函数 | 所有输⼊流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | ⼆进制输⼊ | ⽂件 |
fwrite | ⼆进制输出 | ⽂件 |
函数具体定义(如果有的我写的不是很清楚可以看我的另外一篇博客,哪里有说去哪里可以看c语言这些语法知识)
int fgetc( FILE *stream );//steam是流的意思,该函数可以从流读取数据输入到要赋值变量
fgetc返回成功读取的字符的ASCLL码值,或返回 EOF 以表示错误或文件结束。 使用 feof 或 ferror 来区分错误和文件结束情况。对于 fgetc,如果发生读取错误,则设置流的错误指示器。
int fputc( int c, FILE *stream );//该函数是把c的内容输出到流,返回值为所写字符的ASCLL码值,EOF表示错误
char *fgets( char *string, int n, FILE *stream );//返回的是读取到的字符串的地址,返回 NULL 表示错误或文件结束条件。使用 feof 或 ferror 确定是否发生错误。
int fputs( const char *string, FILE *stream );//如果成功,这些函数中的每一个都返回一个非负值。出现错误时,fputs返回EOF
int fscanf( FILE *stream, const char *format [, argument ]... );//stream后面就和scanf写法一样,下面也有代码示例
int fprintf( FILE *stream, const char *format [, argument ]...);
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );//前面有
//对fputc的示例
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
for (int ch = 'a'; ch <= 'z'; ch++)
fputc(ch, pf);
fclose(pf);
pf = NULL;
}
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//对fgets和fprintf的示例
char ch[100];
fgets(ch, 27, pf);
fprintf(stdout, "%s", ch);//读取pf指向的文件(test.txt)里面的的内容并且输出到屏幕
//stdout标准输出
//后面的概念例子
printf("\n");
printf("%d\n", ftell(pf));//ftell返回现在光标和首位置的偏移量
rewind(pf);//指针(光标)回到首位
int c = 0;
//fgetc的例子
while ((c = fgetc(pf)) != EOF)//fgetc结束返回EOF
{
printf("%c", (char)c);//用fgetc读取,其实也可可以不用强行转换为char
//因为字符本来就是以ASCLL储存
}
fclose(pf);
pf = NULL;
return 0;
}
//fscanf
int main()
{
int a = 0;
printf("输入一个数:");
if (fscanf(stdin, "%d", &a) != 1)
{
printf("读取失败");
return 1;
}
printf("\n(printf输出)输入值为%d:", a);
fprintf(stdout, "\n(fprintf输出)输入值为%d:", a);
}
struct stu
{
char name[20];
int age;
float score;
}s;
//fscanf演示
int main()
{
s.age = 20;
s.score = 80.5f;
*s.name = "zhangsan";
sprintf(s.name,"%d %f",s.age,s.score);
printf("%s", s.name);
int a = 0;
char ch[20] = "123";
if ((sscanf(ch, "%d", &a) == 1))
printf("\nch = %d", a);
return 0;
}
int main()
{
FILE* pf = fopen("data.txt", "r");//data.txt事先我已经放了数据,这边就不写了
if (pf == NULL)
{
perror("fopen");
return 1;
}
FILE* cpf = fopen("data_copy.txt", "w");
if (cpf == NULL)
{
perror("fopen");
return 1;
}
char ch;
while ((ch = fgetc(pf)) != EOF)
{
fputc(ch, cpf);
}
fclose(pf);
fclose(cpf);
cpf = NULL;
pf = NULL;
return 0;
}
⽂件的随机读写
很显然有顺序读写那么应该也有随机读写
fseek:int fseek( FILE *stream, long offset, int origin );//steam是文件流,offset是要偏移的量,origin必须是三个常量之一。返回值:如果成功,fseek 返回 0。否则,它返回一个非零值。在无法进行查找的设备上,返回值未定义。
fseek 函数将与流关联的文件指针(如果有)移动到与原点偏移字节的新位置。流上的下一个操作发生在新位置。在打开用于更新的流上,下一个操作可以是读取或写入。参数 origin 必须是以下常量之一,在 STDIO 中定义.
origin的三个常量 (SEEK_CUR 文件当前位置,SEEK_END 文件末尾,SEEK_SET 文件起始位置)
rewind:void rewind( FILE *stream );//让光标回到起始位置
ftell:long ftell( FILE *stream );//返回值是光标现在位置相对于起始位置的偏移量
例子:
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char ch[20] = "hello world";//现在文件内是hello
fputs(ch,pf);//数组名就是数组首元素地址
fclose(pf);
pf = NULL;
//
FILE* ptf = fopen("test.txt", "r");
if (ptf == NULL)
{
perror("fopen");
return 1;
}
char c = fgetc(ptf);
printf("打印h:%c\n", c);//打印一个字符,应该是h
//我想打印w,那么光标应该指向w
//hello world,起始位置相对于w的偏移量应该是6(空格也是字符),但是已经读取了h,现在指针应该偏移了一位
//所以相对于现在位置只要控制指针偏移5为即可
fseek(ptf, 6,SEEK_SET);//相对于起始位置
printf("相对于起始位置打印w:%c\n", fgetc(ptf));
//现在光标(指针)应该来到了o
printf("%d\n",ftell(ptf));//可以用ftell确定一下,应该是7
//我想打印d
fseek(ptf, -1, SEEK_END);//相对于末尾
printf("相对于末尾位置打印d:%c\n", fgetc(ptf));
//指针又来到了末尾(读取一次指针自动下移一位)
fseek(ptf, -1, SEEK_CUR);//再次打印d
printf("相对于当前位置打印d:%c", fgetc(ptf));
fclose(ptf);
ptf = NULL;
return 0;
}
⽂件读取结束的判定
我在文件的顺序读写中有提到设置流的错误指示器,这里讲一下它们是什么
feof 和 ferror
牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。
feof 的作⽤是:当⽂件读取结束后,判断是读取结束的原因是否是遇到⽂件尾结束,对于是遇到文件末尾结束返回非零,否则返回零。
ferror的作用是:用于检测流上的错误标签是否被设置(在文件的读取或写入时如果发生错误会设置一个错误标签),如果错误标签被设置返回非零,否则返回零。所以是文件结束后,判断是否是发生错误导致文件结束,
int main()
{
FILE* pf = fopen("test.txt", "r");//fputc写入应该是'w',故意写错测试
if (pf == 0)
{
perror("fopen");
return 1;
}
for(char c = 'a'; c <= 'z'; c++)
fputc(c, pf);
if (feof(pf))//是末尾返回非零,反之零
{
printf("\n遇到文件末尾,读取正常结束");
}
else if (ferror(pf))//有错误标记返回零,反之非零
{
perror("fputc");
}
return 0;
}
可以看到错误提示
⽂件缓冲区
一点注意事项
ANSIC 标准采⽤“缓冲⽂件系统”处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为
//这是我从别处搞来的
#include <stdio.h>
#include <windows.h>
//VS2019 WIN11环境测试
int main()
{
FILE*pf = fopen("test.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
//注:fflush 在⾼版本的VS上不能使⽤了
printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭⽂件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}