支付宝红包
京东盲盒抽奖
幸运转盘
秒杀
自营热卖
支付宝红包

你是否还记得c语言的这些文件操作?

云酿雨 1年前   阅读数 129 0

前言

学完c已经较长时间了,在学习期间陆陆续续的整理了多篇关于c的详细知识点文章,此篇文章将是博主对于c整理的倒数第二篇文章----------c语言的文件操作.下面博主也放出了一些之前的文章链接,大家若是有兴趣,也可以观看观看.

温馨提示,此文较长,大家可以根据目录选择观看


c语言操作符 基础指针 一篇面试题的经历
数据存储原理 基础结构体 指针进阶
数组与指针关系 指针与数组经典题 字符串与内存函数
自定义类型,结构体进阶 动态内存详解 ^ 异或运算技巧
& 与运算技巧 c的小细节

正文引言

此文目录1 ~ 4都是属于理论知识,稍微有点枯燥,大家有兴趣可以看看,没兴趣可以跳过

并不会影响后面的阅览.

目录


1. 什么是文件

现实生活中,文件常常指一些关于 政治理论,时事政策,学术研究等文章.

计算机中,磁盘上的存储的一份份重要信息是文件.

但在程序设计中,我们一般谈的文件有两种: 程序设计,数据文件

  • 程序文件

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

即一切用c程序得来的相关文件.

  • 数据文件

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

总而言之,就是使用文件操作函数中涉及到的 文件,称为数据文件

本章讨论的是数据文件

在此之前,我们所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。 其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件.

2. 文件名

文件名就是一个文件的名字,是一个唯一的文件标识,以便用户识别与引用.

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

例如: C:\code\comment\text.txt

文件路径: C:\code\comment\

主干名: text

后缀: .txt

3. 文件类型

根据数据的组织形式,数据文件被称为文本文件或者二进制文件

数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是 二进制文件

如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件

3.1 一个数据在内存中是怎么存储的呢

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

例如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节,如下图:

image-20210531154208544

4. 文件缓冲区

什么是文件缓冲区?我们先不着急介绍文件缓冲区. 但是大家在写一段涉及到输入输出的程序时,一定会有这样的经历吧.

比如写这样一段程序在运行时,会有下面的图:

#include <stdio.h>
int main()
{
     
    char str[50] = {
     0};
    printf("请输入一句话:\n");
    scanf("%s",str);
    printf("你输入的语句是:%s\n",str);
    return 0;
}

image-20210531155748658

在我们输入的地方,只要我们还没有 按下回车,就可以在这里继续输入,修改想要的输入的东西.一旦按下回车,这段数据就会被送入

程序中,然后运行,无法再修改.这个输入停留区有个专有名词,叫做输入缓冲区.

输入缓冲区的好处是,在我们还没有确定发送数据时候,还可以进行修改想要发送的数据,防止输入人按键出错引起的一些后果.

当然, 输入缓冲区也是有一定大小的,如果缓冲区满了,就不会听从你的指挥,自动就把数据发送出去.而缓冲区的大小是由编译器决定的

同理,文件缓冲区也是这样,当我们建立文件(在内存中),在文件里面写东西时候,这些数据是并没有发送到硬盘保存的,此时的数据还是停留在计算机中的一个叫做 文件缓冲区的地方.当我们觉得完毕了,就点击保存,此时数据就被真的送进了硬盘. (从内存到硬盘)

如果我们想要看一下某个文件的内容,就会打开文件,然后就看到了文件的内容.但是我们看到的内容并不是我们想象那样一下子就有了,在从硬盘到内存中,也存在一个缓冲区,开始一一读取硬盘内容,当缓冲区满了后,就把读取的内容发送到内存.

图解:

image-20210531162539798


这里有一段关于缓冲区的有趣程序,可以深切体会到缓冲区的概念.

#include <stdio.h>
#include <windows.h>
int main()
{
     
    while (1)
    {
     
        Sleep(1000);  //沉睡1秒
        printf("哈");
    }
    return 0;
}

你会发现,和你想像的结果完全不一样,你本来以为是,隔一秒就打印一个 ,但是事实上是,隔了很久突然打印一大坨.

这就是缓冲区的原因.


5. 文件指针

每个被使用的文件都会内存中开辟一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及 文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.

而我们只需要一个该结构体指针就可以进行维护,操作文件文件信息区,以达到对文件的操作.该结构体指针就叫做 文件指针

image-20210531164832759

6. 文件的打开与关闭

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

在编写程序时,一旦打开一个文件的同时,就会返回一个FILE*的指针变量指向该文件信息区,也就是相当于建立了指针和文件的关系.

  1. 文件的打卡是通过函数 fopen()
  2. 文件的关闭是通过函数fclose()

6.1 打开文件函数 fopen

使用: FILE* fopen(const char* filename,const char* mode);

解释:

  • filename ----文件名,在目录2中介绍过,需要写完好的路径,路径有下面两种:
    • 相对路径-----需要打开的文件相对于该程序文件的相对位置
    • 绝对路径-----从盘路径开始,直到最后. 比如:C:\code\mode\tets.txt
  • mode — 文件打开的方式,传入的是一个字符串.
  • FILE* —如果打开成功,返回值是一个文件指针, 目录5介绍过. 如果打开失败,返回空指针

打开模式表格:

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

小结:

  • 带了b的模式就是二进制文件.
  • 在打开一个不曾有的文件时候,除了二进制的读写操作模式外,其余的只要是的操作都是建立一个该新文件.

示例:

#include <stdio.h>
int main ()
{
     
  	FILE * pFile; //创建文件指针
  	pFile = fopen ("myfile.txt","w"); //只写模式打开,如果没有文件"myfile.txt",就会在该代码文件同级目录下创建.
    
    if(pFile == NULL) // 必须要判断是否打开成功
    {
     
        perror("打开失败原因");
        return -1;
    }
  	return 0;
}

图解:

image-20210531205443916

清晰的看到,在运行程序之前,同级目录下并没有myfile.txt,但是运行之后建立了一个空文件myfile.txt(之所以是空文件,是因为并没有读写数据)


6.2 文件的关闭

使用: int fclose(FILE * stream)

解释: stream 需要关闭的文件,传入的值是该文件指针.

返回值: 如果成功关闭,返回0;没有成功关闭返回非0

示例:

#include <stdio.h>
int main()
{
     
   	FILE * pFile; //创建文件指针
  	pFile = fopen ("myfile.txt","w"); 
    if(pFile == NULL) // 必须要判断是否打开成功
    {
     
        perror("打开失败原因");
        return -1;
    }
    fclose(pFile);  //关闭文件
    return 0;
}

6.3 总结

fopenfclose函数是连在一起使用的,有时候我们经常会忘记关闭文件.

所以,为了防止忘记关闭,我们一定要养成一个好习惯,那就是在打开文件时候.就立马写一个关闭.

然后再在两个函数之间写其他代码.

同时,在打开文件时候必须要检查打开是否成功.

例如:

#include <stdio.h>
int main()
{
     
    FILE* pFILE = fopen("abc.txt","w");
    if(pFile == NULL) // 必须要判断是否打开成功
    {
     
        perror("打开失败原因");
        return -1;
    }
    .........//这里才是第三步骤写代码的地方
    fclose(pFILE);
    return 0;
}

7. 文件的读写

现在就是进入了高潮部分,是文件操作的重要部分.

其中文件的读写分为两种,一种是 顺序读写, 一种是 随机读写.

下面博主将会一一介绍两种读写方式,以及相关使用.


7.1 顺序读写

顺序读写函数:

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

在详细介绍各个函数的使用之前,大家还需要知道一个概念-------流(stream)

是一种实际数据输入输出映射出的一种 理想化数据流, 是不是太抽象了??? 哈哈哈,没事下面用白话解释.

我们输入数据是怎么输入的? — ---- – -- – 使用键盘把想要输入的数据进行输入. – — – — --- — --- ---- 键盘就是一个输入流.

我们输出数据是怎么输出的?— ---- — ----- 把想要的数据逐渐的打印在屏幕上面.------- – -- - - - - - - - — 屏幕就是一个 输出流

而我们习惯把 键盘称为 stdin,标准输入流.

且我们习惯把 屏幕称为stdout,标准输出流.

而现在我们需要努力做的事情就是 把各种文件理解为 键盘屏幕.

如果该文件达到的是键盘作用,就称该文件是 输入流

如果该文件达到的是屏幕作用,就称该文件是 输出流


下面开始介绍各个顺序读写函数

① fgetc 字符输入函数

使用文档: int fgetc(FILE* stream)

stream此时是一个 输入流,起着键盘作用.代表着 从一个文件中挨个读取字符并返回该字符的ASCII值.

示例:

#include <stdio.h>
/* 这里有一个fgetc.c文件,文件内容如下: If you are feeling that life just cannot be any worse for you, it can be challenging to think positive thoughts. When we are stressed, depressed, upset, or otherwise in a negative state of mind because we perceive that "bad things" keep happening to us, it is important to shift those negative thoughts to something positive. */
int main()
{
     
    FILE* stream;
    char str[100] = {
      0 };
    int i = 0;

    stream = fopen("fgetc.txt", "r"); // 以只读模式打开
    if (stream == NULL) // 必须要判断是否打开成功
    {
     
        perror("打开失败原因");
        return -1;
    }

    //顺序读取80个字符.
    for (int i = 0; i < 80; i++)
    {
     
        str[i] = fgetc(stream);
    }

    //关闭文件
    fclose(stream);

    //打印读取的内容
    printf("%s", str);
    return 0;
}

结果:

image-20210531231051023


② fputc 字符输出函数

使用文档: int fputc(int c,FILE* stream)

c是一个字符. 有人会问,c不是int类型吗? 但是大家要记得哦~ ,字符也是整型家族的成员哦,且是以ASCII值形式存储.

stream此时是 输出流,起着屏幕作用.

#include <stdio.h>
/* 此时同级目录下有一个空文件fputc.txt */
int main()
{
     
   FILE* stream;
   stream = fopen("fputc.txt","w");
   if(stream == NULL)
   {
     
       perror("打开失败原因:");
       return -1;
   }
   fputc('c',stream);
   
   fclose(stream);//不要忘记关闭文件
   return 0;
}

结果: 成功读入一个字符c

image-20210531234508516

③ fgetc与fputc函数的结合使用.

该两个函数结合使用的作用是,把一个文件的内容拷贝到另一个文件中去.

示例:

/* 同级目录下fgetc.txt中的内容是 If you are feeling that life just cannot be any worse for you, it can be challenging to think positive thoughts. When we are stressed, depressed, upset, or otherwise in a negative state of mind because we perceive that "bad things" keep happening to us, it is important to shift those negative thoughts to something positive. 同级目录下fputc.txt中无内容. */
#include <stdio.h>
int main()
{
     
    FILE* instream = fopen("fgetc.txt","r");//只读模式打开
    FILE* outstream = fopen("fputc.txt","w");//只写模式打开
    
    if((instream == NULL) || (outstream == NULL)) //检测是否打开失败
    {
     
        perror("错误原因:");
        return -1;
    }
    
    int ch = 0; //准备开始读取
    while((ch = fgetc(stream)) != EOF) //EOF是文件结束标志,代表着文件读取完毕
    {
     
        fputc(ch,outstream);
    }
    
    fclose(instream);//不要忘记关闭文件
    fclose(outstream);
    return 0;
}

在程序未有运行前,两个文件的内容:

image-20210531235657441

在程序运行后,两个文件的内容:

image-20210531235900793

④ fgets 文本输入函数

使用文档: char* fgets(char* string,int n,FILE* stream)

解释: 他的功能是从某一个输入流(键盘作用)中读取n个字符,放到string中去.

所以stream是输入流(键盘作用),n代表多少个字符,string代表着需要放的位置.

如果成功读取,返回string,如果失败返回NULL

示例:

/* 同级目录下有一个fgets.txt文件,内容是: Mom bought me a pair of skating shoes at my fifth birthday. From then on, I developed the hobby of skating.It not only makes me stronger and stronger, but also helps me know many truths of life.I know that it is normal to fall, and if only you can get on your feet again and keep on moving, you are very good! */

#include <stdio.h>
int main()
{
     
    char string[100] = {
     0};
    FILE* stream = fopen("fgets.txt","r");//只读模式打开
    
    if(stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }
    
    fgets(string,100,stream);//读取100个字符
    
    fclose(stream);//不要忘记关闭文件
    printf("%s",string);//检验是否成功
    return 0;
}

结果:

image-20210601090500370

⑤ fputs 文本输出函数

使用文档: int fputs(const char* string,FILE* stream)

解释: string是目标字符串,即它的内容会被写进stream.

— ---stream是输出流,起着屏幕作用.

示例:

/* 同级目录下有一个fputs.txt文件. 内容是空的. */
#include <stdio.h>
int main()
{
     
    char string[100] = {
     0};
    FILE* stream = fopen("fputs.txt","W");//只写模式打开
    if(stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }
    
    printf("请输入你想要输出的句子:\n");
    scanf("%s",string);
    
    fputs(string,stream);
    fclose(stream);//不要忘记关闭文件
    return 0;
}

程序运行时输入的句子:

image-20210601092026312

运行后fputs文件的内容:

image-20210601092059408

⑥ fgets函数与fputs函数结合

实现把一个文件的内容拷贝到另一个文件.

/* 同级目录下有一个文件fgets.txt 内容为: qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm 加上回车共236个字节.同级目录下游一个文件fputs.txt文件内容空 */
#include <stdio.h>
int main()
{
     
    //打开文件
    FILE* instream = fopen("fgets.txt","r");
    FILE* outstream = fopen("fputs.txt","w");
    //检测打开成功与否
    char string[240] = {
     0};
    if((instream == NULL)||(outstream == NULL))
    {
     
        perror("错误原因:");
        return -1;
    }
	int i = 3;
    while(i-->0)
    {
     
        fgets(string,80,instream);//开始执行,79是26个字母的三倍加一个\n 
        //但是为什么要写80个字节呢? 这是因为fgets函数是读取n-1个字符,并且在第n个位置补\0
    	fputs(string,outstream);
    }
 //关闭文件 
    fclose(instream);
    fclose(outstream);
    return 0;
}

运行前:

image-20210601093421750

运行后:

image-20210601103656660

⑦ fscanf 格式化输入函数

使用文档:int fscanf(FILE* stream,const char* format[,arguement]...)

解释: stream是输入流(起着键盘作用), format格式其实就是就是我们使用scanf时候写的东西,比如"%s",s,表示给s输入字符串. format就是这样

示例:

/* 在统计目录下有一个fscanf.txt文件 内容为: I-love-you 1234 123.456 */
#include <stdio.h>
int main()
{
     
    FILE* stream = fopen("fscanf.txt", "r");
    if (stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }

    char s[20] = {
      0 };
    int a = 0;
    float b = 0.0;
    fscanf(stream, "%s", s);
    fscanf(stream, "%d", &a); //注意这里哦,必须有取地址,是和scanf一模一样的.
    fscanf(stream, "%f", &b);

    printf("s的值是%s\na的值是%d\nb的值是%f", s, a, b);
    
    fclose(stream);//必须关闭文件
    return 0;
}

image-20210601104456443

⑧ fprintf 格式化输出函数

使用文档: int fprintf(FILE* stream,const char* format[,arguement]...)

解释: stream是一个输出流,起着屏幕作用. format格式同理,与printf()格式一样的.

示例:

/* 同级目录下有一个fprintf.txt文件,内容为空. */
#include <stdio.h>
ing main()
{
     
    char s[20] = {
     0};
    int a = 0;
    float b = 0.0;
    FILE* stream = fopen("fprintf.txt","w");
    if(stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }
    printf("请输入字符串:\n");
    scanf("%s",s);
    
    printf("请输入整数:\n");
    scanf("%d",&a);
    
    printf("请输入浮点数:\n");
    scanf("%f",&b);
    
    fprintf(stream,"s的值是%s\na的值是%d\nb的值是%f",s,a,b);
    fclose(stream);
    return 0;
}

运行过程中输入内容:

image-20210601111007663

运行后文件内容:

image-20210601111036500

⑨ fscanf与fprintf的结合

/* 同级目录下有一个fscanf.txt文件 内容为 I-very-like-you 132 123.123001 同级目录下有一个fprintf.txt文件 内容空. */
#include <stdio.h>
int main()
{
     
    char s[20] = {
     0};
    int a = 0;
    float b = 0.0;
    
    FILE* instream = fopen("fscanf.txt","r");
    FILE* outstream = fopen("fprintf.txt","w");
    if((instream == NULL) || (outstream ==NULL))
    {
     
        perror("错误原因:");
        return -1;
    }
    
    
    
    fscanf(instream,"%s%d%f",s,a,b);
	fprintf(outstream,"s的值是%s\na的值是%d\nb的值是%f",s,a,b);
    
    fclose(instream);
    fclose(outstream);
    return 0;
}

程序运行前,两个文件内容:

image-20210601114233207

程序运行后,两个文件内容:

image-20210601114325321

⑩ fwrite 二进制输出函数

使用文档: size_t fwrite(const void* buffer,size_t size, size_t count,FILE* stream)

解释:buffer中读取countsize类型大小的元素,然后输出到stream中去.

stream输出流,屏幕作用.

size元素类型大小 ,一般是这样写sizeof(int), sizeof(char)等.

注意: 输出的是二进制.

示例:

#include <stdio.h>
/* 在同级目录下存在一个fwrite.txt文件 */
struct Book
{
     
    char Bname[20];
    int Price;
    char Writer[20];
    float height;
};


int main()
{
     
    struct Book p = {
     "斗罗大陆",25,"唐家三少",123.123};
    FILE* stream = fopen("fwrite.txt","wb");//注意我的打开方式.
    if(stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }
    
    fwrite(&p,sizeof(struct Book),1,stream);
    //关闭文件
    fclose(stream);
    return 0;
}

运行之后,fwrite.txt文件内容:

image-20210601141939785

我们可以看到,有一些东西我们是不认识的,这就是因为二进制存储原因.


⑪ fread 二进制输入函数

使用文档: size_t fread(void* buffer,size_t size,size_t count,FILE* stream)

解释: stream是输入流,起着键盘作用, 该函数的作用是,从stream中读取countsize类型大小的元素,然后放到buffer中.

size 是元素类型大小,一般这样写sizeof(int) ,sizeof(char)等等

count是需要读取的字节大小.

返回值: 注意!!!这里有点特殊,在最后小节会讲到. 他若成功读取返回count,失败读取就返回小于count.

注意: fread读取的格式是二进制.

示例:

#include <stdio.h>
/* 在上面已经有一个fwrite写的二进制文件,现在我们挨个拿出来就行. */
struct Book
{
     
    char Bname[20];
    int Price;
    char Writer[20];
    float height;
};

int main()
{
     
    struct Book p;
    FILE* stream = fopen("fwrite.txt","rb");//注意打开方式
    if(stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }
    
    
    fread(&p,sizeof(p),1,stream);
    
    printf("书名是%s\n价格是%d\n作者是%s\n书高是%f",p.Bname,p.Price,p.Write,p.height);
    fclose(stream);
    return 0;
}

运行结果:

image-20210601142030908


总结:

上面所介绍的都是顺序读写函数.

除了fwritefread是只适合文件操作外,其他都是 适合所有输入输出流.

比如下面两段代码,是等价的.都是键盘输入,屏幕输出

image-20210601142905844

对比一组函数:

scanf / fscanf / sscanf

printf / fprintf / sprintf

  • scanfprintf只针对标准输入输出流— --- — stdinstdout
  • fscanffprintf针对的是所有输入输出流
  • sscanfsprintf中也是针对所有输入输出流.------前者是从字符串中读取格式化数据.----后者是把所有数据变成字符串

7.2 随机读写

7.1小结所讲的全是顺序读写的函数,即是挨个挨个读取.

现在我们要开始讲解不是挨个读取的函数了,可以按照自己的意愿进行读取

① fseek 随机读取函数(可自由定位文件指针所指位置)

使用文档: int fseek(FILE* stream,long offset,int origin)

解释:

stream 输入流,代表从哪里读取.

offset 偏移量,代表偏移多少开始读取

origin 指针位置,它具有三个值,分别是:

  • SEEK_CUR 文件指针所指的当前位置
  • SEEK_SET 文件最开始位置
  • SEEK_END 文件末尾位置

作用: 成功执行后,就把文件中指针指向的位置改变了

返回值 如果成功读取返回0,如果未成功返回非0.

示例:

/* 假设有同级目录下有一个文件TXT.TXT 内容是: abcdefghi */
#include <stdio.h>
int main()
{
     
    FILE* stream = fopen("TXT.TXT","r");
    if(stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }
    int ch = 0;
    fseek(stream,3,SEEK_SET);         //仔细看,我们是与其他函数搭配使用的.
    printf("读取到字符%c\n",fgetc(stream));
    
    fseek(stream,2,SEEK_CUR);		  //仔细看,我们是与其他函数搭配使用的.
    printf("读取到字符%c\n",fgetc(stream));
    
    fseek(stream,-3,SEEK_END);		  //仔细看,我们是与其他函数搭配使用的.
    printf("读取到字符%c\n",fgetc(stream));
    fclose(stream);
    return 0;
}

运行结果:

image-20210601152504763

有人可能会疑惑,第二个应该是f吧,怎么变成g了?

注意哦~~~~,我们第一次调用fseek后,使用了 fgetc函数,他是顺序读写函数,使用它一次之后,指针就会自动往后调用一次

② ftell 返回文件指针(不是真的文件指针,应该叫内容指针)相对于起始位置偏移量

使用文档: long ftell(FILE* stream);

解释: stream 输入流,即需要读的文件

返回值: 返回偏移量

示例:

/* 假设有同级目录下有一个文件TXT.TXT 内容是: abcdefghi */
#include <stdio.h>
int main()
{
     
    FILE* stream = fopen("TXT.TXT","r");
    if(stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }
    int ch = 0;
    fseek(stream,3,SEEK_SET);  //此时内容指向已经变了位置,偏移量为3
    
    printf("偏移量为%d",ftell(stream)); //使用ftell
    fclose(stream);
    return 0;
}

运行结果:

image-20210601153417534

③ rewind函数,让文件指针回到起始位置

使用文档:void rewind (FILE* stream)

示例:

/* 假设有同级目录下有一个文件TXT.TXT 内容是: abcdefghi */
#include <stdio.h>
int main()
{
     
    FILE* stream = fopen("TXT.TXT","r");
    if(stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }
    int ch = 0;
    fseek(stream,3,SEEK_SET);  //此时内容指向已经变了位置,偏移量为3
    printf("偏移量为%d\n",ftell(stream)); //使用ftell
    
    
    rewind(stream);//调用rewind
    printf("偏移量为%d",ftell(stream)); //使用ftell
    
    fclose(stream);
    return 0;
}

运行结果:

image-20210601153844103


8.文件结束的判定

① EOF,文件结束标志.

说完了各种文件操作,但是大家有没有发现一个问题呢?

就是当完全读取一个文件是时候,程序是怎么知道这个文件读取完毕了呢,也就是说在哪里就算读取结束了.

这归功于 EOF,它是默认存在文件最后位置的.我们无法看见他.EOF的值是-1;

小例子:

#include <stdio.h>
/*假设同级目录下有一个空文件TXT.TXT*/
int main()
{
     
    FILE* stream = fopen("TXT.TXT","r");
    if(stream == NULL)
    {
     
        perror("错误原因:");
        return -1;
    }
    int ch = 0;
    ch = fgetc(stream);
    printf("%d",ch);
    fclose(stream);
    return 0;
}

运行结果:

image-20210601155938441

因为文件为空,所以第一个读取到的就是EOF,值为-1;


② feof 文件结束判定函数

使用文档: int feof(FILE* stream)

解释: stream需要判断的流.

返回值: 成功读取,返回非0值. 失败读取,返回0.

PS:

经常有人直接利用feof的返回值进行判断文件是否读取结束,但是这是错误的使用.

feof正确的使用方法是与ferror搭配使用来检测,文件读取结束的原因,是检测原因

ferror的参数只有一个------FILE* stream,返回值也是int,当返回非0时候,表示故障导致结束.

同时,是具有记忆功能的,当读取故障时,会保存故障标记,以让ferror使用

文本文件示例:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
     
    int c; // 注意:int,非char,要求处理EOF.
    FILE* stream = fopen("test.txt", "r");
    if(stream == NULL) //判断文件读取是否打开成功
    {
     
        perror("File opening failed");
        return EXIT_FAILURE;
   }
    
   //下面是在循环读取,一旦发生错误,会返回EOF,同时 流,即stream,会记忆故障标记
   while ((c = fgetc(stream)) != EOF) // 标准C I/O读取文件循环
   {
      
       putchar(c);
   }
    
 //判断是什么原因结束的
   if (ferror(stream))   // 如果返回真,代表故障读取导致的结束.
       puts("I/O error when reading");
   else if (feof(stream)) //如果是真,代表这是正常读取结束导致的结束.
       puts("End of file reached successfully");
   fclose(stream);
   return 0;
}

二进制文件示例

#include <stdio.h>
#define SIZE 5
int main(void)
{
     
    double a[SIZE] = {
     1.0,2.0,3.0,4.0,5.0};
    double b = 0.0;
    size_t ret_code = 0;
    FILE *stream = fopen("test.bin", "wb"); // 必须用二进制模式
    fwrite(a, sizeof(*a), SIZE, fp); // 写 double 的数组
    fclose(stream);
    stream = fopen("test.bin","rb");
    
    
    // 读 double 的数组
    while((ret_code = fread(&b, sizeof(double), 1, stream))>=1) //若读取失败,会返回小于1的数,同时流(stream)记忆故障标记
    {
     
         printf("%lf\n",b);
    }
    
    if (feof(stream))  // 如果返回真,代表故障读取导致的结束.
        printf("Error reading test.bin: unexpected end of file\n");
    else if (ferror(stream))   //如果是真,代表这是正常读取结束导致的结束.
    {
     
        perror("Error reading test.bin");
    }
    fclose(stream);
    fp = NULL;
}










注意:本文归作者所有,未经作者允许,不得转载

全部评论: 0

    我有话说: