write in front

大家好,我是謓泽,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
2021年度博客之星物联网与嵌入式开发TOP5~2021博客之星Top100~2022博客之星Top63~作者周榜84﹣作者总榜704~阿里云专家博主 &阿里云星级博主~掘金优秀创作者⇿InfoQ创作者⇿51CTO红人⇿全网访问量50w+
本文由 謓泽 原创 CSDN首发 如需转载还请通知⚠
个人主页-謓泽的博客_CSDN博客
欢迎各位→点赞 + 收藏⭐️ + 留言​
系列专栏-
【C】系列_謓泽的博客-CSDN博客
✉️我们并非登上我们所选择的舞台,演出并非我们所选择的剧本

文件操作⇢目录

write in front

为什么使用文件

什么是文件

程序文件

数据文件

文件名

文件指针

文件的打开和关闭

fopen()打开文件

参数的介绍

返回值

fclose() 关闭文件

fopen()代码示例

文件的顺序读写

fgetc() → 字符输入函数

fputc() → 字符输出函数

fputc() 代码示例

“三个流”

文件操作函数代码示例

fgetc() 代码示例​​​​​​​

fputs() 写入”字符串”

fgets()从流中获读取”字符串”

fprintf()格式化输出函数

fscanf()格式化输入函数

​​​​​​​fread()fwrite()二进制读/写函数​​​​​​​

​​​​​​​fwrite()fread()代码示例​​​​​​​

​​​​​​​文件的随机读写​​​​​​​

fseek() -重新定位流位置指示器

ftell() -获取流中的当前位置

​​​​​​​文本文件和二进制文件​​​​​​​

文本文件→

二进制文件→

讲解desu

​​​​​​​文件读取结束的判定​​​​​​​

错误使用 feof()

文件缓冲区

最后


为什么使用文件

首先来说下为什么使用文件操作吧,在前面的内容写过一篇通讯录的文章,实际上那个通讯录哪怕我们能够使用动态内存分配给它完成了。但是,依旧存在很大的问题。

实际上在通讯录那个程序当中,我们给cmd 当中输入指令的时候。此时的数据是会被存放在内存当中的。当我们程序要退出的时候,我们在通讯录当中所输入的数据就自然而然的不存在了。当我们需要下次运行的时候,数据又要重新输入。那么此时这就非常的难受。因此,我们输入的数据当中必须是要放在硬盘当中才能够保存数据。

我再来打个比方吧:我们玩游戏的时候,有些游戏是不是需要保存当前数据。此时,当我们玩了半个小时的游戏。我们需要进行保存数据是吧,不然你这半小时所玩的东西就会丢失,也就是游戏当中不会替你去保存当前的数据。就会回到你上一次所保存的数据,张三同学不知道我这样说你是否明白了(doge)(☆-v-)

于是,我们想要实现数据不丢失的话,就必须要了解这个文件操作的使用,明白了mie张三同学。


什么是文件

文件实际上就是我们在硬盘上,所看到的这些东西都是文件,包括[C]盘当中也都是文件。

但是在我们的程序设计当中一般都是会有两种文件的↓

  • 程序文件

包括源文件的后缀名.c

目标文件(Windows环境后缀为.obj)

可以执行程序(Windows环境后为.exe)

那么张三你现在应该知道编译运行过程是什么了吧,张三:不知道,我

那么这里说下实际上也就是上面按照顺序的编译过程:

.c源程序[编译到],obj目标文件再[链接],exe执行文件。

所以,计算机是不可以直接执行由任意高级余元编写的程序.


  • 数据文件

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


文件名

文件名是文件存在的标识,操作系统根据文件名来对其进行控制和管理。不同的操作系统对文件命名的规则略有不同,即文件名的格式和长度因系统而异。为了方便人们区分计算机中的不同文件,而给每个文件设定一个指定的名称。由文件主名和扩展名组成。

文件名包含③部分:文件路径+文件名主干+文件后缀

例如:c:\code\test.txt ,为了方便起见,文件名表示通常被称之为文件名。


文件指针

在缓冲文件系统当中,关键的概念实际上是”文件类型指针”,简称“文件指针”

每个被使用的文件都在内存中开辟了一个相对应的文件的信息区,用来存放文件的相关信息。这些信息都是保存在一个结构体变量当中的,该结构体是由系统进行声明的,取名为:FILE

下面就是 vs 2013 当中编译环境所提供的 stdio.h 头文件中有以下文件类型声明↓

#ifndef _FILE_DEFINEDstruct _iobuf {char *_ptr;int _cnt;char *_base;int _flag;int _file;int _charbuf;int _bufsiz;char *_tmpfname;};typedef struct _iobuf FILE;//重命名产生

当然,这个 FILE 在不同编译器上的设计可能是不同的,不是绝对的。

每当我们打开一个文件的时候,系统会根据文件的情况自动会创建一个FILE结构的变量,并进行填充其中的信息。当然我们在使用的时候无需关心这些。

一般都是通过一个FILE的指针来进行维护这个结构体变量,使用起来更加方便。

此时,我们就可以创建一个FILE*的指针变量↓

FILE* pf;

定义 pf 是一个指向FILE类型数据的指针变量。可以使 pf 指向某一个文件的文件信息区也就是指向 FILE 当中结构体的信息。那么通过该文件的信息区就可以访问该文件。也就是说,通过文件指针变量能够找到与它所相互关联的文件。


文件的打开和关闭

文件在读写的时候就应该先打开文件

文件在使用结束之后就应该关闭文件

在编写程序的时候,在打开文件的同时,都会返回一个FILE的指针变量指向的文件,也就相当于建立了指针和文件的关系。

ANSIC 规定了使用 fopen() 函数来打开文件,同时 fclose() 函数是用来关闭文件的。

那么接下来介绍下fopen()


fopen()打开文件

fopen() 函数声明方式如下↓

FILE * fopen ( const char * filename, const char * mode );

打开文件的方式↓

打开文件名在参数filename中指定的文件,并将其与一个关联起来,该流可以在将来的操作中由返回的FILE指针识别。

  • 这个时候张三同学说”流”是什么啊。
  • 张三同学不要着急,刚想说那这里说说什么是”流”。

“流”:是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为”流”。

流上允许的操作以及如何执行这些操作由mode参数定义。

如果已知返回的流不引用交互式设备(参考setbuf →设置流缓冲区),则默认情况下它是完全缓冲的。

返回的指针可以通过调用fclose文件解除关联。所有打开的文件在正常程序终止时自动关闭。

参数的介绍

filename 包含要打开的文件名称的C语言字符串。它的值应该遵循运行环境的文件名规范,并且可以包含一个路径(如果系统支持的话)。

mode 包含文件访问模式的C语言字符串。它可以是如下所示↓

  1. “r” read(只读):打开文件进行输入操作。该文件必须存在,文件不在error。
  2. “w” write(只写):为输出数据,打开一个文本文件。如果指定的文件不存在的话,则会建立一个新的文件。
  3. “a”追加:打开文件,在文件的末尾输出。输出操作总是在文件的末尾写入数据,并展开它。重新定位操作(fseek, fsetpos,倒带)被忽略。如果文件不存在,则创建该文件。
  4. “r+”读取/更新:打开一个文件进行更新(包括输入和输出)。该文件必须存在。
  5. “w+”写入/更新:创建一个空文件并打开以进行更新(包括输入和输出)。如果一个同名的文件已经存在,它的内容将被丢弃,并且该文件将被视为一个新的空文件。
  6. “a+” append/update(读写):打开一个文件进行更新(包括输入和输出),所有输出操作都在文件的末尾写入数据。重新定位操作(fseek, fsetpos, rewind)影响下一个输入操作,但输出操作将位置移回文件末尾。如果文件不存在,则创建该文件。

返回值

如果文件被成功打开,该函数将返回一个指向file对象的指针,该指针可用于在将来的操作中标识流。

否则,返回一个空指针。在大多数库实现中,errno变量在失败时也被设置为特定于系统的错误代码。

那么在演示 fopen()打开文件的示例之前,我们先了解下什么是关闭文件。


fclose() 关闭文件

​​​fclose()函数声明方式如下↓

int fclose ( FILE * stream );

关闭文件的方式↓

关闭与流关联的文件并解除关联。

所有与流关联的内部缓冲区都与流分离并刷新:任何未读输出缓冲区的内容都被写入,任何未读输入缓冲区的内容都被丢弃。

即使调用失败,作为参数传递的流将不再与文件或其缓冲区相关联。

  • 参数→stream

指向FILE对象的指针,该对象指定要关闭的流。


fopen()代码示例

接下来我们来打开一个文件来康康,分别示例 r 和 w 代码示例如下↓

#define _CRT_SECURE_NO_WARNINGS 1#includeint main(void){FILE* pf = fopen("test.txt", "r");//文件没在if (pf == NULL){perror("fopen");return 1;}printf("文件存在\n");//关闭文件fclose(pf);pf = NULL;return 0;}

运行结果:此时,文件是存在的。

那我们把 test.txt 的文件给进行删除,看看此时的运行结果。

当我们把 test.txt 的文件给进行删除,返回的是空指针。

张三:那我们在 fopen()的mode 参数 换成 “w” 来试下看下会出现什么样子的情况。

ヾ(^▽^*)))好哒!

先说下,文件在的情况下。实际上文件在的情况下和上面的情况都是一模一样的,这里就不再追述了,自己可以试下。重点讲下文件没在的情况。

此时,我们运行这段代码看看会有什么神奇的事情会发生(●’◡’●)

FILE* pf = fopen("test.txt", "w");//修改下参数即可!

让我们来康康运行结果吧,不要眨眼( •̀ .̫ •́ )✧

张三:什么情况,它竟然文件存在,不对啊我们不是把 test.txt 文件删除了。它怎么会存在呢。这……

“张三同学一看你就是没看我上面写的博客”(⌐■_■),你再去上面我写的参数mode介绍那里在仔细的看看。

张三:原来如此,那我们赶紧看看那个文件不存在建立了一个新的文件没有。好的(☆-v-)

从上述截屏当中我们可以发现,它自己雀氏是新建了一个test.txt 的文件。

张三:这该不会是你偷偷创建的吧(doge),我:

当然,这里还有很多参数mode感兴趣的小伙伴们,可以自己尝试下。这里就不再一一介绍了。不然,有点多,博主手痛不想打这么多的字(๐॔˃̶ᗜ˂̶๐॓)


文件的顺序读写

​​​文件的顺序读写:实际上就是怎么样才能把我所写的数据一一的写在文件当中。

这里再次说下“流”的概念,虽说在上面已经说过了。”防止大家忘记,说的就是你张三”

“流”:是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为”流”。

在写代码的时候,我先介绍下文件的顺序读写的函数的一些使用功能。


fgetc() → 字符输入函数

⚡️函数的声明方式如下⚡️

int fgetc ( FILE * stream );

从流中获取字符↓

  • 返回指定流的内部文件位置指示器当前指向的字符。然后,内部文件位置指示符被推进到下一个字符。
  • 如果调用时流位于文件结束位置,则函数返回EOF并设置流的文件结束指示符(feof)。
  • 如果发生读错误,该函数返回EOF并设置流的错误指示符(ferror)。

参数↓

  • 指向标识输入流的FILE对象的指针。

返回值↓

  • 如果成功,则返回字符读取(提升为int值)。返回类型为int,以适应特殊值EOF,表示失败。
  • 如果位置指示符位于文件结束位置,函数返回EOF并设置流的EOF指示符(feof)。
  • 如果发生了其他的读取错误,函数也会返回EOF,但会设置它的错误指示符(ferror)。

✨适用于输入流当中✨


fputc() → 字符输出函数

⚡️函数的声明方式如下⚡️

int fputc ( int character, FILE * stream );

将字符写入流↓

  • 将一个字符写入流并使位置指示器向前移动。字符被写入到流的内部位置指示器所指示的位置,然后该位置指示器自动前进1。

参数↓

  • character:要写的字符的 int 提升。写入时,该值在内部转换为 unsigned char
  • stream:指向标识输出流的FILE对象的指针。

返回值↓

  • 如果成功,则返回所写的字符。如果发生写错误,则返回EOF并设置错误指示器(ferror)

✨适用于输出流当中


​​​​​​​fputc() 代码示例​​​​​​​

那么这里我们就用 fputc() 函数开始写入文件在 test.txt 文件上写入“c”

代码示例如下↓

#define _CRT_SECURE_NO_WARNINGS 1#includeint main(void){FILE* pf = fopen("test.txt", "w");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//写文件fputc('c', pf);//关闭文件fclose(pf);pf = NULL;return 0;}

张三同学:那么我们看看是否写入了数据( ̄▽ ̄)”

从上述图中可以看出成功写入了数据‘c’,当然我们要注意下即使我们只要以‘w’的形式去打开的时候,即使原来文件当中有数据内容也会被清空。

张三:真的是太神奇了(-/*˃̶ᗜ˂̶/*-)

这里注意下是可以进行多个的假设我们要 cpp 的话,直接在加下就可以了。如下代码所示↓

fputc('c', pf);fputc('p', pf);fputc('p', pf);

特别注意一点假设我们是这样的↓

fputc('1001', pf);

它这里写入文件数据当中会是都是按照最后一位的数字来的,也就是数字1。

“三个流”

在这里再说说”流”的概念 FILE*,实际上C语言只要运行起来就默认打开了三个流↓

  1. stdin → 标准输入流 → 键盘
  2. stdout → 标准输入流 → 屏幕
  3. stderr → 标准错误流 → 屏幕

那这不就可以配合我们上面所说的函数去使用了吗。

张三:对啊,毕竟它们实际上也是FILE*啊(●’◡’●)

那么,假设我需要用fputc()在屏幕上给输出信息,就用 stdout 不就可以了吗。

代码示例如下↓

#define _CRT_SECURE_NO_WARNINGS 1#includeint main(void){FILE* pf = fopen("test.txt", "w");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//写文件fputc('c', stdout);fputc('p', stdout);fputc('p', stdout);//关闭文件fclose(pf);pf = NULL;return 0;}

运行结果如下

​​​​​​​文件操作函数代码示例​​​​​​​

fgetc() 代码示例​​​​​​​

那么与之相反的fgetc() 就适合与输入流,那么我们这次就用“r”进行读文件,本身也是用这个来进行读文件的。代码示例如下↓

#define _CRT_SECURE_NO_WARNINGS 1#includeint main(void){FILE* pf = fopen("test.txt", "r");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//读文件for (int i = 0; i < 10; i++){int ret = fgetc(pf);printf("%c", ret);}//关闭文件fclose(pf);pf = NULL;return 0;}

聪明的小伙伴想到了没有,多个直接用循环即可。但是fgetc()必须要包含到循环。

运行结果

Cyuyanyyds,注意:这里我们的 test.txt 文件是 Cyuyanyyds。

单个直接这样即可(☆-v-),张三你明白了没。张三:属实get到了(ノ*・ω・)ノ

int ret = fgetc(pf);printf("%c", ret);

fputs() 写入”字符串”

当然还有些函数例如下↓

fputs() → 可以写入文件按照一行进行写入”字符串” 。

fputs("Cyuyan\n",pf);fputs("ttdyyyds\n",pf);

改变下这个以及写入的是写入是 “w”即可 !( •̀ .̫ •́ )✧

从这里我们可以发现成功了,成功的写入进去了并且是以”字符串”的形式来写入进去的。

还是写下代码吧。ヾ(≧▽≦*)o

#define _CRT_SECURE_NO_WARNINGS 1#includeint main(void){char arr[10] = { 0 };FILE* pf = fopen("test.txt", "w");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//写文件fputs("Cyuyan\n", pf);fputs("ttdyyyds\n", pf);//关闭文件fclose(pf);pf = NULL;return 0;}

fgets()从流中获读取”字符串”

fgets() →从流中获读取”字符串”。

fgets()函数声明方式如下↓

char * fgets ( char * str, int num, FILE * stream );
  • str→指向一个字符数组的指针,在这个数组中,读取的字符串将被复制。
  • num → 在这里复制到str的最大字符数(包括结束的空字符),这个 num 是要 – 1的。当你输入的是4,那么它的这个实际上只会获取3个字符。
  • stream → 指向标识输入流的FILE对象的指针。Stdin可以作为从标准输入中读取的参数。
#define _CRT_SECURE_NO_WARNINGS 1#includeint main(void){char arr[10] = { 0 };FILE* pf = fopen("test.txt", "r");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//读文件fgets(arr, 7, pf);printf("%s\n",arr);//关闭文件fclose(pf);pf = NULL;return 0;}

运行结果

Cyuyan

从这里我们可以看出只有六个字符,所以我们可以看出 num – 1 的,最多只能读六个要留’\0’的一个位置。


fprintf()格式化输出函数

fprintf()函数声明方式如下↓

int fprintf ( FILE * stream, const char * format, ... );

将由format指向的C语言字符串写入流。如果format包含格式说明符(以%开头的子序列)

那么format后面的附加参数将被格式化并插入到结果字符串中,替换它们各自的说明符。

format形参之后,函数期望的附加参数至少与format所指定的相同。

  • 参数如下↓

stream→指向标识输出流的FILE对象的指针。

format→包含要写入流的文本的C字符串。它可以选择性地包含嵌入的格式说明符,这些格式说明符将被后续附加参数中指定的值所替换,并按照请求进行格式化。

介绍完fprintf()让我们来用代码示例讲解下,如何写入一个结构体文件 放在 test.txt 文件上。

代码示例如下↓

#define _CRT_SECURE_NO_WARNINGS 1#includestruct student{char name[20];//学生名字char sex[5];//学生性别char id[20];//学生学号int age;//学生年龄};int main(void){struct student s = { "zhangsan", "nan", "10", 18 };FILE* pf = fopen("test.txt", "w");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//写文件fprintf(pf,"%s %s %s %d",s.name, s.sex, s.id, s.age);//关闭文件fclose(pf);pf = NULL;return 0;}

从上述截屏当中我们成功的用 fprintf() 进行了格式化输出(*^-^*)

张三:你该不会是自己加上去的吧(doge~),我:

当然,如果你要达到换行的效果也是可以的加在‘\n’换行符即可。


fscanf()格式化输入函数

如果你会用上面的格式化输出函数,那么这个函数也就会了。

fscanf()​​​​​​​函数声明方式如下↓

int fscanf ( FILE * stream, const char * format, ... );

从流中读取格式化数据。

从流中读取数据,并根据参数格式将其存储到附加参数所指向的位置。附加的参数应该指向已经分配的对象,其类型由格式字符串中相应的格式说明符指定。

  • 参数如下↓

stream→指向FILE对象的指针,该对象标识要从中读取数据的输入流。

format→C语言当中的字符串,包含一个字符序列,控制如何处理从流中提取的字符…..

那么现在我们进行读文件。示例代码如下↓

#define _CRT_SECURE_NO_WARNINGS 1#includestruct student{char name[20];//学生名字char sex[5];//学生性别char id[20];//学生学号int age;//学生年龄};int main(void){ struct student s = {"张三","你好","嗯",886};FILE* pf = fopen("test.txt", "r");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//读文件fscanf(pf,"%s %s %s %d",s.name,s.sex,s.id,&(s.age));//打印printf("%s %s %s %d", s.name, s.sex, s.id, s.age);//关闭文件fclose(pf);pf = NULL;return 0;}

成功写入进去结构体当中的结构体类型的值\^o^/

//读文件scanf(stdin,"%s %s %s %d",s.name,s.sex,s.id,&(s.age));//打印fprintf(stdout,"%s %s %s %d", s.name, s.sex, s.id, s.age);

如果你是这个样子实际上也是没有任何问题的,本质上f无论是输出还是输入都和没有的差不多知识把相对应的东西改下就可以了意思是一样的。

我再把这三个流放在下面怕各位同学还要上去找(o゚v゚)ノ

  • stdin → 标准输入流 → 键盘
  • stdout → 标准输入流 → 屏幕
  • stderr → 标准错误流 → 屏幕

​​​​​​​fread()fwrite()二进制读/写函数​​​​​​​

两个函数是以二进制当中去读取文件的分别是↓

  • fread → 二进制输入/读。
  • fwrite → 二进制输出/写。

fread()​​​​​​​函数声明方式如下↓

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

从流中读取count元素数组,每个元素的大小为size字节,并将它们存储在ptr指定的内存块中。

流的位置指示器被读取的总字节数提前。

如果成功读取的总字节数是(size*count)。

  • 参数如下↓

ptr→指向内存块的指针,该内存块的大小至少为(size*count)字节,转换为void*类型。

size→要读取的每个元素的大小(以字节为单位)。Size_t是一个无符号整型类型。

count→元素的数量,每个元素的大小为字节大小。

stream指向指定输入流的FILE对象的指针。

  • 返回值如下↓

返回成功读取的元素总数。如果这个数字与count参数不同,则在读取时发生了读取错误或到达了文件结束符。在这两种情况下,都设置了合适的指示器,可以分别用ferror和feof检查。
如果size或count为零,则函数返回零,而流状态和ptr指向的内容都保持不变。

fwrite()实际上它两的参数值都一样,这里就不再说了↓

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

不对,还是有不一样的,不知道你发现了没有 doge (o゚v゚)ノ


​​​​​​​fwrite()fread()代码示例​​​​​​​

fwrite()代码示例如下↓

#define _CRT_SECURE_NO_WARNINGS 1#includestruct student{char name[20];//学生名字char sex[5];//学生性别char id[20];//学生学号int age;//学生年龄};int main(void){ struct student s = {"张三","你好","嗯",886};FILE* pf = fopen("test.txt", "w");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//写文件(二进制形式写)fwrite(&s, sizeof(struct student), 1, pf);//关闭文件fclose(pf);pf = NULL;return 0;}

成功写入但是从上面图中可以看出会出现乱码,原因实际上就是它是以二进制形式写的。这里字符串我们能看懂是因为二进制写进去和文本写进去的形式都是一样的。整数或者浮点数写进去的概念完全是不一样的。

那么我们试试fread()到底能不能看的懂(´▽`ʃ♡ƪ)

fread()代码示例如下↓

#define _CRT_SECURE_NO_WARNINGS 1#includestruct student{char name[20];//学生名字char sex[5];//学生性别char id[20];//学生学号int age;//学生年龄};int main(void){ struct student s = { 0 };FILE* pf = fopen("test.txt", "r");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//读文件fread(&s, sizeof(struct student), 1, pf);//打印printf("%s %s %s %d\n", s.name, s.sex, s.id, s.age);//关闭文件fclose(pf);pf = NULL;return 0;}

从这里我们可以知道我们用fread()就 可以读懂了。好耶o(* ̄▽ ̄*)o】

张三:‍好耶~,我:#########@%¥******

​​​​​​​文件的随机读写​​​​​​​

当然在上述我所说的都是顺序的读写,那么现在我们开始讲解下什么是随机读写。

张三:那个謓泽为什么需要有随机读写呢,我用文件读写它难道不香吗(⊙x⊙;)

我:张三同学出现文件的随机读写绝对是有它的一个好处的,不然为什么它会被出现呢。那么就来和你说说文件随机读写的好处。

好处→速度快,便于进行数据处理。这个就是文件随机读写的好处。

但是,有好处必有坏处。事物都是会具有两面性的。

缺点→占用内存较大。

我:张三同学你明白了没。

张三:GET到了(●’◡’●),但是文件什么是随机读写。

如下图所示↓

fseek() –重新定位流位置指示器

fseek()​​​​​​​​​​​​​​函数声明方式如下↓

int fseek ( FILE * stream, long int offset, int origin );
  • 重新定位流位置指示器

将与流关联的位置指示器设置为一个新位置。

对于以二进制模式打开的流,新位置是通过在origin指定的参考位置上添加偏移量来定义的。

对于以文本模式打开的流,offset要么为零,要么为之前调用ftell时返回的值,而origin必须为SEEK_SET。

如果函数调用这些参数的其他值,支持取决于特定的系统和库实现(不可移植)。成功调用文件结束符后,流的文件结束符内部指示器将被清除。

  • 参数功能如下↓

stream→指向标识流的FILE对象的指针。

offset→二进制文件:从原始文件偏移的字节数。

origin→作为偏移量参考的位置。它由中定义的下列常量之一指定,专门用作该函数的参数如下↓

  1. SEEK_SET:开头的文件。
  2. SEEK_CUR:文件指针的当前位置。
  3. SEEK_END:最后的文件。
  • 这里我们用SEEK_CUR 来举例子如下代码所示↓
#define _CRT_SECURE_NO_WARNINGS 1#includeint main(void){FILE* pf = fopen("test.txt", "r");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//读取文件int ret = fgetc(pf);printf("%c", ret);//SEEK_CUR→调整文件的指针fseek(pf, 2, SEEK_CUR);ret = fgetc(pf);printf("%c", ret);ret = fgetc(pf);printf("%c", ret);ret = fgetc(pf);printf("%c", ret);//关闭文件fclose(pf);pf = NULL;return 0;}

运行结果

上述图中不知道你看懂了没有,我画的应该还不错吧(✿◕‿◕✿)

ftell() -获取流中的当前位置

ftell()函数声明方式如下↓

long int ftell ( FILE * stream );
  • 获取流中的当前位置

返回流的位置指示器的当前值。

对于二进制流,这是从文件开始的字节数。

对于文本流,数值可能没有意义,但仍然可以使用fseek将位置恢复到相同的位置(如果使用ungetc返回的字符在被读取时仍然挂起,该行为是未定义的)。

  • 参数如下↓

stream→指向标识流的FILE对象的指针。

  • 返回值↓

如果成功,将返回文件指针对于起始位置的偏移量。

失败时,返回-1L,并将errno设置为特定于系统的正值。

  • 示例代码如下↓还是上面的代码为例
#define _CRT_SECURE_NO_WARNINGS 1#includeint main(void){FILE* pf = fopen("test.txt", "r");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//读取文件int ret = fgetc(pf);printf("%c", ret);//SEEK_CUR→调整文件的指针fseek(pf, 2, SEEK_CUR);ret = fgetc(pf);printf("%c", ret);ret = fgetc(pf);printf("%c", ret);ret = fgetc(pf);printf("%c", ret);int rets = ftell(pf);printf("%d", rets);//关闭文件fclose(pf);pf = NULL;return 0;}

ftell() 可以告知我们偏移量的值的大小是多少。

拓展→rewind(FILE * stream)能让文件指指针位置回到起始的地址。

​​​​​​​文本文件和二进制文件​​​​​​​

文本文件

一种计算机文件,它是一种典型的顺序文件,其文件的逻辑结构又属于流式文件。特别的是,文本文件是指以ASCII码方式(也称文本方式)存储的文件,更确切地说,英文、数字等字符存储的是ASCII码,而汉字存储的是机内码。文本文件中除了存储文件有效字符信息(包括能用ASCII码字符表示的回车、换行等信息)外,不能存储其他任何信息。说简单点,文本文件实际上就是把内存文件转换成ASCll码的值,最后存到文件当中去。

二进制文件

包含在ASCII及扩展 ASCII字符中编写的数据或程序指令的文件。计算机的文件基本上分为二种:二进制文件和 ASCII(也称纯文本文件),图形文件及文字处理程序等计算机都属于二进制文件。这些文件含有特殊的格式及计算机代码。ASCII 则是可以用任何文字处理程序阅读的简单文本文件。说简单点,二进制这种文件是把内存这种二进制数据不加任何的转换直接写到文件当中去的。

文本文件这不用说是很简单的,那么我们来用代码演示下二进制文件。假设我们要写入变量num 的数字 1000,让我们来康康。示例代码如下↓

讲解desu

#define _CRT_SECURE_NO_WARNINGS 1#includeint main(void){int num = 1000;FILE* pf = fopen("test.txt", "wb");//返回值判断if (pf == NULL){perror("fopen");return 1;//返回}//写入文件fwrite(&num, sizeof(int), 1, pf);//关闭文件fclose(pf);pf = NULL;return 0;}

温馨提示:使用上面的模式说明符,文件将作为文本文件打开。为了将文件作为二进制文件打开,必须在模式字符串中包含一个”b”字符。这个额外的”b”字符可以被添加到字符串的末尾从而形成以下复合模式。所以,我们这里的 mode 参数是”wb”。

那么让我们一起看下运行结果

那么有人会说为什么这个是二进制啊。当然如果你想知道它是不是二进制很简单。如果你是使用 vs 的编译器的话可以直接↓

双击(✿◕‿◕✿)

那么 1000 是不是上面的这些数字,我们可以算一算。

首先十进制转换成二进制:00000000000000000000 0011 1110 1000

那么接下来我们转换成十六进制:00 00 03 E8

张三:哇塞学会了(★ ω ★)

不错!那么我们可以知道这个是没有任何的问题的。这种进制转换是必须要拿捏的。

​​​​​​​文件读取结束的判定​​​​​​​

错误使用 feof()

在文件读取的过程当中,不能使用feof()的函数的返回值直接用来判断文件是否是结束的。

而是在应用当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。

  1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc()) 或者 NULL(fgets())
  • 例如:fgetc()判断是否为EOF
  • 例如:fgets()判断返回值是否为NULL

EOF→通常在文本的最后存在此字符表示资料结束。

二进制文件的读取结束判断,判断返回值是否小于实际当中要读的个数。

  • fread判断返回值是否小于实际要读的个数。

文件缓冲区

文件是指存储在外部存储介质上的、由文件名标识的一组相关信息的集合。由于CPU 与 I/O 设备间速度不匹配。为了缓和 CPU 与 I/O 设备之间速度不匹配矛盾。文件缓冲区是用以暂时存放读写期间的文件数据而在内存区预留的一定空间。使用文件缓冲区可减少读取硬盘的次数。​​​​​​​

文件缓冲区是用以暂时存放读写期间的文件数据而在内存区预留的一定空间。通过磁盘缓存来实现,磁盘缓存本身并不是一种实际存在的存储介质,它依托于固定磁盘,提供对主存储器存储空间的扩充,即利用主存中的存储空间, 来暂存从磁盘中读出(或写入)的信息。 主存也可以看做是辅存的高速缓存, 因为,辅存中的数据必须复制到主存方能使用;反之,数据也必须先存在主存中,才能输出到辅存。

一个文件的数据可能出现在存储器层次的不同级别中,例如,一个文件数据通常被存储在辅存中(如硬盘),当其需要运行或被访问时,就必须调入主存,也可以暂时存放在主存的中。磁盘高速缓存当中大容量的辅存常常使用磁盘,磁盘数据经常备份到磁带或可移动磁盘组上,以防止硬盘故障时丢失数据。有些系统自动地把老文件数据从辅存转储到海量存储器中,如磁带上,这样做还能降低存储价格。

相信大家对 getchar() 函数不陌生,当我们在键盘当中输入字符并按下回车的时候,我们所输入的数据会被存放在 缓冲区 当中去。那么在这里放到缓冲区的好处是什么?

举个例子,我们知道计算机CPU的处理速度很快的,而我们键盘的输入速度总是比不过CPU的处理速度,那么CPU就得一直等着键盘输入完,这样很浪费资源。于是,我们党键盘输入完了,再让CPU一次性处理,这样就会大大地提高效率。就好比,老师提问问题如果提出一个问题,让一个同学回答 又 或者一次提十个问题,让十个同学来回答,这样绝对是一次提出十个问题的效率会高很多。

又比如,我们的打印机打印文档,打印机的处理速度是很慢的,所以我们会将文档输出到打印机的缓存中去,这样打印机就可以自行慢慢打印,而不必占用CPU资源。


最后

如果这篇文章有帮助到你的话,不妨三连支持博主下(ง •_•)ง 你的支持就是对我写作的最大的动力,需要交流学习可以添加博主的微信即可。

★最后★点赞 关注 收藏 == 学会✔