目录

1. 为什么使用文件

2. 什么是文件

2.1 程序文件

2.2 数据文件

2.3 文件名

3. 文件的打开和关闭

3.1 文件指针1️⃣

3.2 文件的打开和关闭2️⃣

⭕相对路径

⭕绝对路径

4. 文件的顺序读写

1.fputc写文件–字符输出函数

2.fgetc(pf)读文件–字符输入函数

3.fputs–文本行输出函数

4.fgets–文本行输入函数

5.fprintf–针对所有输出流(文件流/stdout)的格式化输出函数

6.fscanf –针对所有输入流(文件流/stdin)的格式化输入函数

为什么需要用到”流”?

stdin和stdout的适用情况

1.fgetc测试:

2.fputc测试:

3.fprintf测试:

4.fscanf测试:

7.sscanf和sprintf


前言:

这篇文章是关于文件是什么、文件的操作、文件的读写函数:fputc、fgetc、fputs、fgets、fprintf、

fscanf、sscanf和sprintf。以及流的概念,与为什么要使用到流,以及stdin和stdout的适用情况的知

识点总结,如有错误欢迎大佬指正,感谢您的支持!

1. 为什么使用文件

我们前面学习结构体时,写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。 我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。 这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。 使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。

2. 什么是文件

磁盘上的文件是文件。 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

2.1 程序文件

包括源程序文件( 后缀为.c , 目标文件( windows环境后缀为.obj , 可执行程序( windows环境后缀为.exe )。

2.2 数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件, 或者输出内容的文件。

区别: 程序的数据都是在内存里面放的,通过printf把数据显示到屏幕上去叫做输出操作,从键盘上读取数据之后,最终放到程序的内存里面去,其实是通过scanf函数的输入操作 能不能把数据从内存放到文件里面去呢” />

2.3 文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。 文件名包含3部分:文件路径+文件名主干+文件后缀 例如: c:\code\test.txt –>意思是:c:\code\ 是文件路径,放在c盘底下的code文件夹底下的test,是文件名主干 .txt是文件后缀 为了方便起见,文件标识常被称为文件名

那c语言是如何来操作文件呢?

3. 文件的打开和关闭

假设我要从冰箱里面拿冰棍–>打开冰箱,拿冰棍,关上冰箱

类比.–>如果想给文件存放数据,或者从文件里面拿数据:打开文件–>写或读数据(使用)–>关闭文件

最核心的是要知道文件如何打开和关闭:

3.1 文件指针1️⃣

缓冲文件系统中,关键的概念是文件类型指针,简称文件指针 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE. 这段话什么意思呢” />文件信息区(可以理解为一个结构体的变量) 这个文件信息区跟文件(.txt)是关联的,是为了描述这个文件的, 文件信息区 里面放了( 文件的名字,文件状态及文件当前的位置等)

那它是如何存放到内存里面的? 创建了一个File结构体变量

struct _iobuf { char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname; }; typedef struct _iobuf FILE;//FILE–>本质是一个结构体类型

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异:

意思是:(操作系统不同不同的c编译器对于FILE结构体是怎么定义的是不相同的,windows对于文件的使用习惯、操作、文件后缀等等,跟linux、macos是不一样的,是由不同的研发团队、不同公司去开发的,所以在对FILE这个结构的定义都是有差异的

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量(跟data.txt文件相关的文件信息区),并填充其中的信息, 使用者不必关心细节。

意思是: 这个变量就是对这个信息区的整合 信息区就是详细的解释

如何维护文件信息区( 也就是结构体变量)? 一般都是通过 一个FILE的指针 来维护这个 FILE 结构的变量,这样使用起来更加方便

FILE* pf;//文件指针变量

图解: 定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件 比如:

3.2 文件的打开和关闭2️⃣

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件

在编写程序的时候,在打开文件的同时,都会返回一个 FILE*的指针变量指向该文件 ,也相当于建立了指针和文件的关系。 ANSIC 规定使用 fopen 函数来打开文件 fclose来关闭文件 参数问题: FILE* pf = fopen(“test.txt”,”w”);// pf(指针)可以通过地址找到这个文件对应的文件信息区,就可以操作这个文件 以下是fopen的打开方式:

这里我要说一下这个打开方式的意思:例如以读的方式打开,就是说,根据拿到的这个文件指针,只有读的权限,没有写的权限一般是和下面文件的输入输出函数是相对应的

文件使用方式 含义 如果指定文件不存在
“r”(只读) 为了输入数据,打开一个已经存在的文本文件 出错
“w”(只写) 为了输出数据,打开一个文本文件建立一个新的文件
“a”(追加) 向文本文件尾添加数据建立一个新的文件
“rb” (只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出数据,打开一个二进制文件建立一个新的文件
“ab” (追加) 向一个二进制文件尾添加数据 出错
“r+” (读写) 为了读和写,打开一个文本文件 出错
“w+” (读写) 为了读和写,建议一个新的文件 建立一个新的文件
“a+” (读写) 打开一个文件,在文件尾进行读写 建立一个新的文件
“rb+” (读写) 为了读和写打开一个二进制文件出错
“wb+” (读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
“ab+” (读写) 打开一个二进制文件,在文件尾进行读和写 建立一个新的文件

实例代码:

#includeint main(){//打开文件//相对路径FILE* pf = fopen("test.txt","w");//当打开test.txt,会在内存中创建一个文件信息区, //这时是把文件信息区的地址放到pf里面去了,if (pf == NULL)//pf可以通过地址找到这个文件对应的文件信息区,就可以操作这个文件{perror("fopen");return 1;} //写文件//关闭文件fclose(pf);//不会把pf置为空指针,可以想象成一个变量,以值传递的形式,所以不能改掉pfpf = NULL;return 0;}

有两个该注意的点:

经过执行之后:

⭕相对路径

这个test.txt会新建出来,但是里面是没有内容的

fopen(“test.txt”,”w”)–>指定的名字test.txt,而且是在这个工程底下去搞得

打开看看:

但是这个时候如果写入东西:

再执行:

里面的内容就销毁掉了:

这时候换成fopen(“test.txt”, “r”)看看

#include

int main()
{
//打开文件
//相对路径
FILE* pf = fopen(“test.txt”, “r”);//这个位置换成”r”

if (pf == NULL)
{
perror(“fopen”);
return 1;
}
//写文件

//关闭文件
fclose(pf);//不会把pf置为空指针,可以想象成一个变量,以值传递的形式,所以不能改掉pf
pf = NULL;

return 0;
}

返回值问题:

返回成功/失败

⭕绝对路径

如果在这个桌面上创建一个文件

右击属性找到路径:

实例代码:

#includeint main(){//打开文件//相对路径 FILE* pf = fopen("C:\\Users\\zzc\\Desktop\\test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//写文件//关闭文件fclose(pf);//不会把pf置为空指针,可以想象成一个变量,以值传递的形式,所以不能改掉pfpf = NULL;return 0;}

留意一下:加多几条’\’的原因是把原来的’\’也转义了,因为中间有个’\t’

这个位置:”C:\Users\zzc\Desktop\test.txt”

说明一下:

如果原来的.txt里面有内容,经过执行以后:

会把原来的内容彻底销毁掉

4. 文件的顺序读写

这些函数的意义:一般与上面fopen的打开方式是相对应的,以什么打开方式就赋予了这个pf指针什么样的权限

功能 函数名适用于
字符输入函数 fgetc所有输入流
字符输出函数fputc所有输出流
文本行输入函数 fgets所有输入流
文本行输出函数fputs所有输出流
格式化输入函数 fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入 fread文件
二进制输出fwrite文件

1.fputc写文件–字符输出函数

fputc(‘a’ + i,pf);//此函数一次写一个字符,所以需要一个循环来输出26个字符

文献:

翻译:

代码实例:

//fputc 写文件#includeint main(){FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//写文件int ch = 0;int i = 0;for (i = 0; i < 26; i++){fputc('a' + i,pf);//此函数一次写一个字符,ascll码值+1}//关闭文件fclose(pf);pf = NULL;return 0;}

效果:

2.fgetc(pf)读文件–字符输入函数

ch = fgetc(pf);//此函数一次读取一个字符

可以看以下执行效果:

文献:

翻译:

代码实例:

//fgetc - 读文件操作#includeint main(){FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1; }//读文件//把26个字面写到文件中int ch=0;//看上面的文献,所以为什么这个地方会用int接收//printf("%c\n", ch);int i = 0;for (i = 0; i < 26; i++){ch = fgetc(pf);//此函数一次读取一个字符printf("%c ",ch);//ascll码值+1}//关闭文件fclose(pf);pf = NULL;return 0;}

代码执行:注意前提是这个文件里面有内容,否则不会输出任何东西

执行:

为什么pf不能++

如果加入一行代码:pf++

原因:对于文件指针进行++操作是不允许的,语法不支持

3.fputs–文本行输出函数

文献:

翻译:

代码实现:

//fputs 写一行数据#includeint main(){FILE* pf = fopen("test.txt", "w");//这里写文件名的话默认是在这个工程底下搞的,写成绝对路径或者其它相对路径,要指定if (pf == NULL){perror("fopen");return 1;}//写一行数组 hello bitfputs("hello bit\n", pf);fputs("hello world\n", pf);// 关闭文件fclose(pf);pf = NULL;return 0;}

输出不换行:

fputs(“hello bit”, pf);
fputs(“hello world\n”, pf);

输出换行: 上面的代码可参考

4.fgets–文本行输入函数

文献:

翻译:

代码实例:以下注释说明了为什么打印hell

//fgets - 读一行数据int main(){FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读char arr[20];fgets(arr,5, pf);//在.txt文件有内容的情况下,这里的参数5说明最多读4个,最后一个位置是留给'\0'的printf("%s\n", arr);//关闭文件fclose(pf);pf = NULL;return 0;}

执行:

5.fprintf–针对所有输出流(文件流/stdout)格式化输出函数

以下可以看到fprintf只是比printf多了一个流指针的参数而已。

与printf的区别:printf是针对标准输出流 (stdout) 的格式化的输出函数

假设要把struct S s = { 100,3.14f,”zhangsan” };信息打印出来那应该怎么写呢” />

#includestruct S{int n;float f;char arr[20];};int main(){struct S s = { 100,3.14f,"zhangsan" };//打开文件FILE* pf = fopen("test.txt", "w");if (NULL == pf){perror("fopen");return 1;}//写文件fprintf(pf, "%d %f %s\n", s.n, s.f, s.arr);//关闭文件fclose(pf);pf = NULL;return 0;}

输出:

6.fscanf –针对所有输入流(文件流/stdin)格式化输入函数

跟fprintf传的文件流指针是一样的

与scanf的区别:是针对标准输入流 (stdin)的格式化的输入函数

#includestruct S{int n;float f;char arr[20];};int main(){struct S s = { 100,3.14f,"zhangsan" };//打开文件FILE* pf = fopen("test.txt", "w");if (NULL == pf){perror("fopen");return 1;}//写文件fscanf(pf, "%d %f %s",&(s.n),&(s.f),s.arr);//数组名arr是首元素地址,所以不需要取地址操作符printf("%d %f %s\n", s.n, s.f, s.arr);//关闭文件fclose(pf);pf = NULL;return 0;}

执行:

为什么需要用到”流”” />如何把数据写进入外部设备,需要对每一种外部设备的操作比较多的了解,例如数据是怎么写进入文件、屏幕、网络、打印机等外部设备,都得知道它们的实现细节(读和写的细节),对于程序员来说太难了。

流的概念:这时候抽象化了一个概念“流”,“流”知道怎么把数据写进各种外部设备的细节,那对于程序员而言,要读取数据就从流里面读,要写数据就写到流里面就可以了,至于这个流怎么跟外部设备交互的是不需要关心的。

流的种类:

stdin标准输入流 stdou标准输出流stderr标准错误流都是FILE*的指针

stdin和stdout的适用情况

回到前面的输入输出函数:

fgetc、fgets、fscanf均适用于文件流以及标准输入流 — stdin

fputc、fputs、fprintf均适用于文件流以及标准输出流 — stdout

1.fgetc测试:

#includeint main(){int ch = fgetc(stdin);//输入printf("%c\n", ch);return 0;}

执行:以下是输入一个字符和输出的情况

2.fputc测试:

int main(){fputc('a', stdout);//输出fputc('f', stdout);fputc('g', stdout);fputc('z', stdout);return 0;}

执行:

3.fprintf测试:

int main(){struct S s = { 1000,3.666f,"hehe" };fprintf(stdout,"%d %f %s\n",s.n,s.f,s.arr);return 0;}

执行:

4.fscanf测试:

int main(){struct S s = { 0 };fscanf(stdin, "%d %f %s",&(s.n),&(s.f),s.arr);fprintf(stdout, "%d %f %s\n", s.n, s.f, s.arr);}

7.sscanf和sprintf

sscanf文献

翻译:

sprintf文献

翻译:

sscanf–把字符串转换成格式化的数据

sprintf–把格式化的数据转换成字符串

struct S{int n;float f;char arr[20];};int main(){//序列化和反序列化struct S s = { 200,3.5f,"wangwu" };//把一个结构体转换成字符串char arr[200] = { 0 };sprintf(arr, "%d %f %s\n", s.n, s.f, arr);printf("字符串的数据: %s\n", arr);//把一个字符串转换成对应的格式化数据struct S tmp = { 0 };sscanf(arr, "%d %f %s", &(tmp).n, &(tmp.f), &tmp.arr);printf("格式化的数据:%d %f %s\n", tmp.n, tmp.f, tmp.arr);return 0;}

执行:

关于文件操作的知识还有下半篇,欢迎大佬指正,感谢支持!