🔥 个人专栏: 《C语言》、《数据结构》、《C++》、《Linux》
🗂️ Gitee仓库: 《C语言》、《数据结构》、《C++》、《Linux》
</> 算法专栏: 《算法精选集》
C文件操作
- 1、fopen & fclose
- 2、fprintf & fputs & fwrite
- 2.1、回顾“新建文件,新建在当前文件”
- 2.2、文件模式
- 2.3、理解:文件内容 & 读写
我们曾经在C语言的学习中,见过了部分常见的关于文件操作的(库)函数。这篇文章中,
- 我们将会复习这些库函数
- 将其中涉及到的知识,与操作系统做一个简单的关联
1、fopen & fclose
FILE*fopen(constchar*pathname,constchar*mode);其中,FILE*我们可能还记得是“文件指针”。实际上FILE*又叫“句柄”。
只要我们拿着FILE*变量,就可以对被打开的文件做任何操作。
分析fopen的参数列表:
const char *pathname
这个参数,很明显就是要我们输入文件所在的路径,
- 如果显示输入文件的路径,
fopen就会打开指定路径下的文件; - 如果不显示输入,即只输入文件名,
fopen就会去默认的**当前路径(CWD)**找同名文件并打开。
也就是说:不带路径,并不意味着不需要路径。
const char *mode
这个参数是文件打开的模式,即文件是“怎样被打开的”。
文件打开的模式有六种:
我们在下面的学习中,会详细介绍这些打开模式。
分析fopen的返回值:
- 成功,返回
FILE*变量 - 失败,返回
NULL,然后错误将会被报告
至于fclose,我们目前理解为“关闭文件”即可。只要使用fopen打开了文件,我们就在最后加上fclose即可。
#include<stdio.h>intmain(){FILE*fp=fopen("log.txt","w");// ...fclose(fp);return0;}2、fprintf & fputs & fwrite
这三个库函数都可以向文件做写入。但是它们之间有细节上的差别。
fprintf
我们知道,printf直接向标准输出流做写入;而fprintf就是指定流做写入,那么fprintf就可以指定一个文件(流),然后写入内容到文件中。
值得注意的是,fprintf和printf一样,对于输入的任意类型的变量,都可以转化成字符串再输入到流中,即做格式化输入。
#include<stdio.h>#include<stdlib.h>intmain(){FILE*pf=fopen("log.txt","w");if(NULL==pf){perror("fopen failed!\n");exit(1);}intcnt=10;while(cnt){fprintf(pf,"Hello world. cnt: %d\n",cnt);--cnt;}if(pf)fclose(pf);return0;}fputs
intfputs(constchar*s,FILE*stream);fputs就是向指定流里写入字符串。
#include<stdio.h>#include<stdlib.h>intmain(){FILE*pf=fopen("log.txt","w");if(NULL==pf){perror("fopen failed!\n");exit(1);}intcnt=10;constchar*message="Hello text.\n";while(cnt--)fputs(message,pf);if(pf)fclose(pf);return0;}fwrite
size_tfwrite(constvoid*ptr,size_tsize,size_tnmemb,FILE*stream);fwrite的作用是向指定流写入任意类型的数组。其中,
size:数组元素的大小(字节数)。比如对于int,我们可以写sizeof(int)nmemb:数组元素的个数
#include<stdio.h>#include<stdlib.h>#include<string.h>intmain(){FILE*pf=fopen("log.txt","w");if(NULL==pf){perror("fopen failed!\n");exit(1);}constchar*str="abc\n";intcnt=10;while(cnt--)fwrite(str,sizeof(str[0]),strlen(str),pf);if(pf)fclose(pf);return0;}2.1、回顾“新建文件,新建在当前文件”
还记得吗:文件新建后,为什么就在“当前路径”?因为有环境变量。
使用系统调用chdir,对当前进程的环境变量PWD做修改,我们就可以设计程序,在指定的路径新建一个文件:
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>intmain(){chdir("/home/yzc");//char pwd[64];//getcwd(pwd, sizeof(pwd));//printf("%s\n", pwd);FILE*pf=fopen("log.txt","w");fclose(pf);return0;}2.2、文件模式
w模式
/* w: Truncate file to zero length or create text file for writing. The stream is positioned at the beginning of the file. */以w模式打开文件:
- 如果文件不存在,新建一个同名文件
- 如果文件存在,
- 清空文件内容
- 从文件的开头,开始写入
还记得吗:想清空文件,我们可以执行命令>log.txt,往文件log.txt中写入“空”。
实际上,输出重定向操作符>,转化成了以w模式打开文件,写入内容,再关闭文件的操作。(w模式打开文件,会清空文件内容)
a模式
/* a: Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file. */a意为"append",“追加”的意思。以a模式打开文件,
- 如果文件不存在,新建文件
- 如果存在,在文件的末尾位置开始写入(不做清空操作)
类比上面关于输出重定向>与w模式关系的讨论,我们也可以得出:追加输出重定向>>,转化成了以a模式打开文件,写入内容,再关闭文件的操作。
r模式
r模式下进行的是读取。
#include<stdio.h>#include<stdlib.h>#include<unistd.h>intmain(){FILE*pf=fopen("log.txt","r");if(NULL==pf){perror("fopen failed!\n");exit(1);}charinbuf[64];while(fgets(inbuf,sizeof(inbuf),pf))// 将 log.txt 的内容读取到 inbuf 中{printf("%s",inbuf);sleep(1);}fclose(pf);return0;}2.3、理解:文件内容 & 读写
文件内容,实际上就相当于一个一维数组
char content[]
在用户的视角下,文件log.txt的内容可能是这样的:
abcd1234而在操作系统的视角下,文件log.txt的内容却是这样的:
abcd\n1234我们可以得出结论:
- 所谓文件内容,其实就是操作系统维护了一个一维数组
char content[]
那么,
- w模式下从文件的
beginning开始写入,就是在content的0下标开始写入 - a模式下从文件的
ending开始写入,就是在content的strlen(content)下标开始写入。这里我们可以理解为,末尾开始写入的时候,content数组会扩容
读写:维护一个
int index
我们如何知道,读写文件,到了哪一个位置?答案是操作系统,维护了一个int index,定位读写content的位置。也就是说index存储的是content的下标值。
所以我们有库函数ftell,告诉我们index此时的位置:
#include<stdio.h>#include<stdlib.h>#include<unistd.h>intmain(){FILE*pf=fopen("log.txt","r");if(NULL==pf){perror("fopen failed!\n");exit(1);}longpos=ftell(pf);while(1){pos=ftell(pf);intch=fgetc(pf);if(ch==EOF)break;elseif('\n'==ch)printf("%ld: \\n\n",pos);elseprintf("%ld: %c\n",pos,ch);sleep(1);}fclose(pf);return0;}但是,由于index只有一个,所以使用 w+ r+ a+ 模式的时候,当我们读 / 写完后,index已经发生了变化;再想写 / 读的话,需要重新调整index。(这就比较麻烦)