文本行的读写
fgets和fputs
int main() { FILE* pf = fopen("data.txt", "w"); if (pf == NULL) { perror("fopen"); return 1; } fputs("abcdef", pf); fclose(pf); pf = NULL; return 0; }
这次我们重新读入,一次读入abcdef这6个字符。因为是同对一个文件进行操作,所有原先写入的数据会被覆盖。我们再次打开data.txt
就可以观察到,文件中只有新写入abcdef。
再来试一试读:
int main() { FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } char ch[20] = { 0 }; fgets(ch, 20, pf);//读num-1个 printf("%s\n", ch); fclose(pf); pf = NULL; return 0; }
fgets的参数依次是,读取后存放的位置,读取的字符个数(n-1)个,读取数据的位置。
我们可以先在读数据的文件中输入长度超过20的字符进行验证。
格式化输入输出
接下来就是fprintf 和fscanf
前边介绍的方法都是字符的读写,而fprintf 和fscanf是对数据进行格式读写,我们直接上代码:
struct S { int a; float s; }; 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.s); fclose(pf); pf = NULL; return 0; }
它可以将不同格式的数据输出到指定位置。和printf很类似,就是前边加上输出的位置。
运行成功后,打开文件就可以看到100,3.140000出现在文件中。
既然有格式化输出,那就必然有从文件中输入。
直接上代码:
struct S { int a; float s; }; 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.s)); printf("%d %f", s.a, s.s); fclose(pf); pf = NULL; return 0; }
我们可以保存之前的数据,再次运行,屏幕上就会输出文件中的数据。fscanf和我们经常适用的scanf也很相似,只是多了一个读取数据的位置。
除此之外还有sscanf和sprintf
我们可以来对比一下这几个函数:
- scanf 从标准输入流读取格式化的数据。
- printf 向标准输出流写格式化的数据。
- fscanf 适用于所有输入流的格式化输入函数
- fprintf 适用于所有输出流的格式化输出函数
sprintf 将格式化的数据写入到字符串中(将格式化的数据转化为字符串),废话不多讲我们直接上代码:
struct S { int a; float s; char str[20]; }; int main() { char ch[30] = { 0 }; struct S s = { 100,3.14f,"hello world" }; sprintf(ch, "%d %f %s",s.a,s.s,s.str); return 0; }
通过调试,观察ch中的数据:
可以观察到它确实将数据转化成了字符串,并存放到了数组当中。
和printf也很类似,只需在前边添加要存放的数组。
sprintf可以将任何类型的数据转换成字符串,那就会有还原的函数,那么这就是sscanf函数的作用。
struct S { int a; float s; char str[20]; }; int main() { char ch[30] = { 0 }; struct S s = { 100,3.14f,"hello world" }; struct S t = { 0 }; sprintf(ch, "%d %f %s",s.a,s.s,s.str); sscanf(ch, "%d %f %s", &(t.a),&(t.s), &(t.str)); printf("%d %f %s\n", t.a,t.s,t.str); return 0; }
sprintf将数据转换成字符串,sscanf将数据还原。通过结构体访问成员进而输出。
sscanf其实也是和scanf很像,它仅仅是在前边添加了一个转换的数组地址。
这里我们继续对文件操作函数进行介绍。
二进制文件输入输出
fread和fwrite
写文件:
struct S { int a; float s; char str[20]; }; int main() { struct S s = { 100,12.56f,"hahaha" }; FILE* pf = fopen("data.txt", "wb");//注意这里不再是w而实wb以二进制形式写入。 if (pf == NULL) { perror("fopen"); return 1; } fwrite(&s,sizeof(struct S),1,pf);写一个结构体数据到文件中 fclose(pf); pf = NULL; return 0; }
创建一个结构体s并进行初始化。fwrite怎么用呢?它的第一个参数是数据的来源,第二个参数是数据的大小,第三个参数是写入的个数,第四个参数是写入的位置。
运行成功后,此时打开文件data.txt观察数据:
结果是乱码,这就是二进制文件,但这并不是谁都读不懂,计算机也可以将数据转换成正常数据。这就是接下来要介绍的fread函数的功能
我们看代码:
struct S { int a; float s; char str[20]; }; int main() { struct S s = { 0 }; FILE* pf = fopen("data.txt", "rb");//注意这里打开文件的方式需要修改rb if (pf == NULL) { perror("fopen"); return 1; } fread(&s, sizeof(struct S), 1, pf); printf("%d %f %s", s.a, s.s, s.str); fclose(pf); pf = NULL; return 0; }
它和fwrite函数传递的参数一致,但函数作用恰好相反。运行程序后就可以将原先写入的二进制文件转变为正常数据。
文件的随机读写
前边我们都是在写文件的顺序读写,其实文件还存在随机读写。关于随机读写的函数有3种
fseek、ftell和rewind
fseek
根据文件指针的位置和偏移量来定位文件指针。
ftell
返回文件指针相对于起始位置的偏移量
rewind
让文件指针的位置回到文件的起始位置
我们直接看应用:
int main() { FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } fseek(pf, 9, SEEK_SET);//文件指针偏移 SEEK_SET初始位置开始偏移 int ch=fgetc(pf); //SEEK_CUR 当前位置开始偏移 printf("%c\n", ch); //SEEK_END 末尾位置开始偏移 int p=ftell(pf);//计算文件内容偏移量 printf("%d\n", p); rewind(pf);//回到起始位置 ch = fgetc(pf); printf("%c\n", ch); fclose(pf); pf = NULL; return 0; }
fseek第一个参数是文件指针,定位要读取的文件,第二个参数是偏移量,第三个参数是文件起始位置。
上述程序运行前可以在文件中初始化为26个字母。运行就可以看到一下结果:
文件操作知识拓展
文本文件和二进制文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
直白点说就是,打开一个文件,我们能看懂的就属于文本文件,看不懂的字符就是二进制文件。
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
例如:整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节。
文件结束判定
被错误使用的 feof
切记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束
文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets)
- fgetc判断是否为EOF.
- fgets判断返回值是否为NULL.
二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
- fread判断返回值是否小于实际要读的个数
这里就不再举例介绍了,这些细节上的问题一定要注意。
文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定
总结
本期内容到此结束,在本篇博客中,向大家介绍了C语言中的文件操作技巧,帮助您更好地管理和处理文件。无论您是初学者还是有经验的程序员,这些技巧都能够为您带来便利和效率。希望您能够在日后的工作和学习中不断运用和完善这些技能!最后,感谢阅读!