C语言-文件操作

于高山之巅,方见大河奔涌

于群峰之上,更觉长风浩荡

C语言-文件操作_第1张图片


目录

文件的简单介绍:

文件名  

文本文件与二进制文件

文件的打开和关闭

流和标准流

流的概念:

 标准流的概念:

文件指针  

文件指针 FILE* 的概念:

文件的打开和关闭  

fopen打开文件函数:

fclose关闭函数:

文件的路径问题

文件输入和输出的概念:

 文件的顺序读写

 顺序读写函数介绍:

 字符串:

fputc写数据

fgetc读数据

文本行: 

fputs写数据

fgets读数据

格式化:

fprintf写数据

fscanf读数据

二进制: 

fwrite写数据

fread读数据

对比一组函数的区别

scanf/fscanf/sscanf printf/fprintf/sprintf

文件的随机读写 

fseek

ftell

rewind

文件读取结束的判定 

 文件缓冲区


接下来我将带领大家一起去学习文件使用的方法~


 在进入本章知识讲解之前,大家在以往的编程中是不是都有这样的困惑

这里用比较简单的代码进行展示:

#include
int main()
{
	int n = 0;
	printf("%d\n", n);
	scanf("%d", &n);
	printf("%d", n);
	return 0;
}

C语言-文件操作_第2张图片

C语言-文件操作_第3张图片


n的初始值为0,我给n赋值为5

但我下一次打开程序,n的值仍然是0

因为n是内存中的数据,一旦程序结束空间就还给操作系统了

数据不能持久性的保存

这就是我们使用文件的原因:

持久化的保存数据


文件的简单介绍:

使用文件的原因

我们写的程序的数据是 存储在电脑的内存 中,如果 程序退出,内存回收,数据就丢失 了,等再次运行程序,是看不到上次程序的数据的,如果要将 数据进行持久化的保存 ,我们可以使文件

文件的类型

1.程序文件
C语言-文件操作_第4张图片

源程序文件(后缀为.c)
目标文件(windows环境后缀为.obj)
可执行程(windows环境后缀为.exe)
2.数据文件
文件的内容不⼀定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件

注意:本篇讨论的都是数据文件


文件名  

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


文本文件与二进制文件

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

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

以下是记事本是以文本文件的形式打开读不懂二进制的所以产生乱码

C语言-文件操作_第5张图片


文本文件:外存上以ASCII码的形式存储,硬盘上以ASCII字符的形式存储的文件

以下是文本文件二进制信息转换成对应的ASCII码值的形式

C语言-文件操作_第6张图片


一个数据在内存中存储的方式:

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


这里看不懂没有关系我们用代码来实现以下:

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

我们这里用二进制的形式打开一个文件

"wb"就是以二进制的形式打开一个文件

#include 
int main()
{
	int a = 10000;
	FILE* pf;
	pf = fopen("test_12_15.txt", "wb");
	fwrite(&a, 4, 1, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

C语言-文件操作_第7张图片

我们发现这个文件以记事本的形式打开是一推乱码

所以它是一个二进制文件


C语言-文件操作_第8张图片

C语言-文件操作_第9张图片

我们在编译器上以二进制的形式读取

我们发现10000在二进制的存储的形式是10 27 00 00


C语言-文件操作_第10张图片

我们知道在VS的编译器下内存是以小端存储

所以要倒过来看:10 27 00 00

总结:

我们发现二进制文件不加转换的输出到外存

以ASCII字符的形式存储的文件就是文本文件

以上内容大家以了解的形式看就行,我们实际编程中用到不多

接下来来到我们的重点

文件的打开和关闭


流和标准流

流的概念:

流是一个系统与一个程序之间形成的一个通道:C程序针对文件、画面、键盘等的数据输⼊输出操作都是通过流操作的。 一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。

 标准流的概念:

C语言程序在启动的时候,默认打开了3个流:
stdin - 标准输入流,在大多数的环境中从键盘输⼊, scanf函数就是从标准输入流中读取数据
stdout - 标准输出流,大多数的环境中输出至显示器界面, printf函数就是将信息输出到标准输出流中
stderr - 标准错误流,大多数环境中输出到显示器界面

哪有什么岁月静好,不过是有人替你负重前行

流就是这样,因为有了流scanf、printf等函数就可以直接进行输入输出操作的


stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为文件指针
FILE* 的文件指针是用来维护流的各种操作的

文件指针  

文件指针 FILE* 的概念:

这个指针是用来存放文件的首地址的

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

我们一般都是⼀个FILE的指针来维护这个FILE结构的变量 ,例如:

FILE* pf;	//文件指针变量
通过文件指针变量能够间接找到与它关联的文件
C语言-文件操作_第11张图片

文件的打开和关闭  

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

2.在打开文件的同时,都会返回⼀个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系
3. fopen 函数来打开文件, fclose 来关闭文件

fopen打开文件函数:

C语言-文件操作_第12张图片

FILE *fopen(const char *filename, const char *mode)
  • filename -- 字符串,表示要打开的文件名称
  • mode--表示文件的打开模式,下面都是文件的打开模式: 

  • C语言-文件操作_第13张图片


    fclose关闭函数:

    int fclose(FILE *stream)
    

    stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了要被关闭的流


#include 
int main()
{
        //打开文件
        FILE* fp = fopen("data.txt", "w");
        //文件操作
        if (fp == NULL)
        {
            perror("fopen");
            exit(0);     //输入错误,直接退出
        }
        //关闭文件
    fclose(fp);
    fp = NULL;
    return 0;
}

文件的路径问题

相对路径相对于当前工作目录的文件路径

例如:这样我们就在编译器的下创建了一个data.txt文件

因为我们还没有开始写数据,所以大小为0kb

C语言-文件操作_第14张图片

例如:我们要创建当前路径的上一级目录中的data.txt的文件

运用点路径

" . "表示当前路径

" .. "表示当前路径的上一个路径

C语言-文件操作_第15张图片

C语言-文件操作_第16张图片

这样我们的文件就创建在了当前路径下的上一个路径


绝对路径从根目录开始的完整文件路径

在使用绝对路径时,需要提供完整的路径信息,包括根目录、路径分隔符以及文件名

例如:我们要创建桌面上的名为data.txt的文件

C语言-文件操作_第17张图片

我们将桌面一个应用的地址拷贝到我们的代码里面:

我们这里加斜杠的原因是为了防止某些字符的转义

C语言-文件操作_第18张图片

C语言-文件操作_第19张图片

这样文件就在我们桌面上创建了

文件输入和输出的概念:

C语言-文件操作_第20张图片

文件输入: 文件输入是指将外部文件中的数据读取到程序中进行处理的过程

文件输出: 是指将程序中的数据写入到外部文件中的过程

 文件的顺序读写


C语言-文件操作_第21张图片


 顺序读写函数介绍:

 C语言-文件操作_第22张图片

 字符串:

C语言-文件操作_第23张图片

int fgetc ( FILE * stream );

作用:从指定的流 stream 获取下一个字符,并把位置标识符往前移动

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流

C语言-文件操作_第24张图片

int fputc ( int character, FILE * stream );

作用:把参数 character 指定的字符写入到指定的流 stream 中,并把位置标识符往前移动

  • character -- 这是要被写入的字符,该字符以其对应的 int 值进行传递
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流

fputc写数据

代码展示:把26个小写字符写到我的文件里面

#include 
int main()
{
        //打开文件
        FILE* fp = fopen(" data.txt", "w");
        //文件操作
        if (fp == NULL)
        {
            perror("fopen");
            exit(0);     //输入错误,直接退出
        }
        for (int i = 0; i < 26; i++)
        {
            fputc('a' + i, fp);
        }
        //关闭文件
    fclose(fp);
    fp = NULL;
    return 0;
}

C语言-文件操作_第25张图片

到这里肯定有小伙伴会有些困惑,顺序表现在哪里呢?

我们画图来说明:

fputc一旦读写一个字符光标就会自动往后移动一位

C语言-文件操作_第26张图片


fgetc读数据
代码展示:将文本文件的数据读到屏幕上
#include 
int main()
{
    //打开文件
    FILE* fp = fopen("data.txt", "r");
    //文件操作
    if (fp == NULL)
    {
        perror("fopen");
        exit(0);     //输入错误,直接退出
    }
    int ch = fgetc(fp);
    for (int i = 0; i < 26; i++)
    {
        printf("%c", ch+i);
    }
    //关闭文件
    fclose(fp);
    fp = NULL;
    return 0;
}

C语言-文件操作_第27张图片

这样我们就把文本文件的信息读取到屏幕上啦

代码练习:写一个代码完成将data1.txt文件内容,拷贝一份到生成的data2.txt文件里面 

代码展示:

#include 
int main()
{                                         //这里要先创建一个data1.txt的文件
    FILE* fp = fopen("data1.txt", "r");   //打开data1.txt负责读取数据 
    if (fp == NULL)
    {
        perror("fopen->data1.txt");       //专门指出data1.txt的错误
        return 1;
    }
    FILE* pf = fopen("data2.txt", "w");
    if (pf == NULL)
    {
        fclose(fp);                       //如果打开失败,就将data1.txt文件关闭
        fp = NULL;
        perror("fopen->data2.txt");
        return 1;
    }
    int ch = 0;
    while ((ch = fgetc(fp)) != EOF)       //将在data1.txt的文件信息读取
    {
        fputc(ch, pf);                    //并写到data2.txt中
    }
    fclose(fp);                           //关闭文件
    fclose(pf);
    return 0;
}

C语言-文件操作_第28张图片

C语言-文件操作_第29张图片



文本行: 

C语言-文件操作_第30张图片

char * fgets ( char * str, int num, FILE * stream );

作用:从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (num-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定

  • str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串
  • num -- 这是要读取的最大字符数。通常是使用以 str 传递的数组长度
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流

C语言-文件操作_第31张图片

int fputs ( const char * str, FILE * stream );

作用:把字符串写入到指定的流 stream

  • str -- 这是一个数组,包含了要写入的以空字符终止的字符序列
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流

fputs写数据

代码展示:

#include 
int main()
{                                         
    FILE* fp = fopen("data.txt", "w");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    fputs("abcdef", fp);
    fclose(fp);
    fp = NULL;
    return 0;
}

C语言-文件操作_第32张图片


fgets读数据

代码展示:

#include 
int main()
{                                         
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    char arr[20] = "*******************";
    fgets(arr,10,fp);
    for (int i = 0; i < 20; i++)
    {
        printf("%c", arr[i]);
    }
    fclose(fp);
    fp = NULL;
    return 0;
}

C语言-文件操作_第33张图片

我们在data.txt文件中保存了10个字符

C语言-文件操作_第34张图片

我们发现它是读取了num-1个也就是9个这是为什么呢?

我们在监视的环境看看

C语言-文件操作_第35张图片

我们发现fgets在读取的时候会给' \0 '预留一个位置

我们以后的编程中要注意:

fgets读数据的时候读取的是num-1个


格式化:

C语言-文件操作_第36张图片

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

作用:从流 stream 读取格式化输入

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流
  • format -- 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符
  • format 说明符形式为 [=%[*][width][modifiers]type=],具体讲解如下:

C语言-文件操作_第37张图片


C语言-文件操作_第38张图片

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

作用:函数把格式化的字符串写入到指定的输出流

C语言-文件操作_第39张图片


fprintf写数据

代码展示:

#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    Node s = { "张三",18,99.9f };
    FILE* fp = fopen("data.txt", "w");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    fprintf(fp,"%s %d %.2f", s.name, s.age, s.score);
    fclose(fp);
    fp = NULL;
    return 0;
}

我要将一个结构体类型的数据写入文件就可以使用fprintf

C语言-文件操作_第40张图片


fscanf读数据

代码展示:

#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    Node s = { 0 };
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    fscanf(fp,"%s %d %f", s.name, &(s.age), &(s.score));//不是数组就要&
    printf("%s %d %.2f", s.name, s.age, s.score);
    fclose(fp);
    fp = NULL;
    return 0;
}

读取一个结构体类型的文本数据

C语言-文件操作_第41张图片


二进制: 

C语言-文件操作_第42张图片

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

fread() 函数读取打开的文件

函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止运行

该函数返回读取的字符串,如果失败则返回 FALSE


C语言-文件操作_第43张图片

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

fwrite() 函数将内容写入一个打开的文件中

函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止运行

如果函数成功执行,则返回写入的字节数。如果失败,则返回 FALSE


fwrite写数据

代码展示:

#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    Node s = { "张三",18,99.9f };
    FILE* fp = fopen("data.txt", "wb");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    fwrite(&s, sizeof(s), 1, fp); 
    fclose(fp);
    fp = NULL;
    return 0;
}

注意:因为这里是以二进制的形式进行写入的,所以文本文件是读不懂的,显示的就是一堆乱码

C语言-文件操作_第44张图片

肯定有小伙伴想问,既然看不懂那有什么用呢?

别着急,我们可以用 fread 去读文件


fread读数据

代码展示:

#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    Node s = { 0 };
    FILE* fp = fopen("data.txt", "rb");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    fread(&s, sizeof(s), 1, fp); 
    printf("%s %d %.2f", s.name, s.age, s.score);
    fclose(fp);
    fp = NULL;
    return 0;
}

C语言-文件操作_第45张图片


 C语言-文件操作_第46张图片


对比一组函数的区别

scanf/fscanf/sscanf printf/fprintf/sprintf

大家看到以上成双成对的函数,是不是会很头疼,它们究竟有什么区别呢?

scanf-针对标准输入(键盘)的格式化输入函数

printf-针对标准输出(屏幕)的格式化的输出函
fscanf-针对所有输入流的格式化输入函数

fprintf-针对所有输出流的格式化输出

sscanf-从字符串中读取格式化数据

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

sscanf/sprintf前面没讲过,我们现在重点讲

sscanf:

C语言-文件操作_第47张图片

int sscanf ( const char * s, const char * format, ...);

sprintf:

C语言-文件操作_第48张图片

int sprintf ( char * str, const char * format, ... );

sprintf的讲解:

#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    Node s = { "zhangsan",18,100};
    char arr[100] = { 0 };
    sprintf(arr, "%s %d %.2f", s.name, s.age, s.score);
    printf("%s\n", arr);
    return 0;
}

C语言-文件操作_第49张图片

将结构体格式化数据转换成字符串的形式

这里重点代码:

    char arr[100] = { 0 };
    sprintf(arr, "%s %d %.2f", s.name, s.age, s.score);
    printf("%s\n", arr);

以字符串的形式输出结构体数据


#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    Node temp = { 0 };
    char arr[100] = "zhangsan 18 100";
    sscanf(arr, "%s %d %f", temp.name, &(temp.age), &(temp.score));
    printf("%s %d %.2f", temp.name, temp.age, temp.score);
    return 0;
}

C语言-文件操作_第50张图片

将字符串的数据转换成格式化数据形式

这里重点代码:

    char arr[100] = "zhangsan 18 100";
    sscanf(arr, "%s %d %f", temp.name, &(temp.age), &(temp.score));
    printf("%s %d %.2f", temp.name, temp.age, temp.score);

以结构体的形式输出字符串的数据


文件的随机读写 


 首先来介绍以下,这里的随机并不是随机数的随机,而我想在哪里读写文件就可以在哪里


fseek

作用:

该函数把文件指针从当前位置向前或向后移动到新的位置

新位置从文件头开始以字节数度量

(文件指针就是我们所谓的光标)

C语言-文件操作_第51张图片

int fseek ( FILE * stream, long int offset, int origin );
stream 必需。规定要在其中定位的文件。
offset 必需。规定新的位置(从文件头开始以字节数度量)。
origin

SEEK_SET:基准位置为文件开头,即offset表示距离文件开头的偏移量

SEEK_CUR:基准位置为文件当前位置,即offset表示距离文件当前位置的偏移量 

SEEK_END:基准位置为文件末尾,即offset表示距离文件末尾的偏移量

现在看不懂没有关系,接下来我将带大家去理解:

我们拿顺序读写来作比较:

#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    int ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    fclose(fp);
    fp = NULL;
    return 0;
}

C语言-文件操作_第52张图片

我们在data.txt中保存了这些字符

C语言-文件操作_第53张图片


我们读写的时候发现

它是顺序读写

每一次读入数据光标就会往后移动

那么我想重新读写我的a怎么办呢?

这个时候就要用到我们的fseek函数


代码展示:
#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    int ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    fseek(fp, -3, SEEK_CUR);
    ch = fgetc(fp);
    printf("%c\n", ch);
    fclose(fp);
    fp = NULL;
    return 0;
}

C语言-文件操作_第54张图片

C语言-文件操作_第55张图片


这种随机读写的方法有多种,这里在介绍几种:

#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    int ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    fseek(fp, 0, SEEK_SET);
    ch = fgetc(fp);
    printf("%c\n", ch);
    fclose(fp);
    fp = NULL;
    return 0;
}
#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    int ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    fseek(fp, -11, SEEK_END);
    ch = fgetc(fp);
    printf("%c\n", ch);
    fclose(fp);
    fp = NULL;
    return 0;
}

以上的代码第四位显示在屏幕上的依然是a

希望大家可以理解,这里就不画图了


ftell

作用:返回文件指针相对于起始位置的偏移量

简单来说就是返回当前光标的位置

long int ftell ( FILE * stream );
#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    int ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    int n = ftell(fp);
    printf("%d\n", n);
    fclose(fp);
    fp = NULL;
    return 0;
}

C语言-文件操作_第56张图片

此时的光标位置就是3,显示出来的字符就是c

但你偏移量(光标)不知道在哪时,就可以用这种方法来找到


rewind

作用:让文件的指针(光标)回到文件的起始位置

void rewind ( FILE * stream );

C语言-文件操作_第57张图片

#include 
typedef struct Stu
{
    char name[20];
    int age;
    float score;
}Node;
int main()
{    
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    int ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    ch = fgetc(fp);
    printf("%c\n", ch);
    rewind(fp);//这个时候光标已经回到起始位置了
    ch = fgetc(fp);//读取的字符就是a
    printf("%c\n", ch);
    fclose(fp);
    fp = NULL;
    return 0;
}

C语言-文件操作_第58张图片


文件读取结束的判定 

feof - 在文件读取结束后,判断是否是因为遇到文件末尾而结束

ferror- 在文件读取结束后,判断是否是因为遇到错误而结束

1.文本文件读取是否结束,判断返回值是否为 EOF fgetc ),或者 NULL fgets
例如
fgetc 判断是否为 EOF 
fgets 判断返回值是否为 NULL 
#include 
#include 
int main(void)
{
	int c; // 注意:int,非char,要求处理EOF
	FILE* fp = fopen("test.txt", "r");
	if (!fp) {
		perror("File opening failed");
		return EXIT_FAILURE;//EXIT_FAILURE就是1的意思,这里相当于枚举常量
	}
	//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
	while ((c = fgetc(fp)) != EOF) 
	{
		putchar(c);
	}
	//判断程序是什么原因结束的
	if (ferror(fp))//如果遇到程序错误而结束,fp返回EOF(-1)为真执行语句
		puts("I/O error when reading");
	else if (feof(fp))//判断程序是否因为到达程序末尾而结束
		puts("End of file reached successfully");
	fclose(fp);
    fp = NULL
}

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

例如

fread判断返回值是否小于实际要读的个数
#include 
enum { SIZE = 5 };
int main(void)
{
	double a[SIZE] = { 1.,2.,3.,4.,5. };
	FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式
	fwrite(a, sizeof * a, SIZE, fp);    // 写 double 的数组
	fclose(fp);
	double b[SIZE];
	fp = fopen("test.bin", "rb");
	size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
	if (ret_code == SIZE) {
		puts("Array read successfully, contents: ");
		for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
		putchar('\n');
	}
	else { 
		if (feof(fp))
			printf("Error reading test.bin: unexpected end of file\n");
		else if (ferror(fp)) {
			perror("Error reading test.bin");
		}
	}
	fclose(fp);
	fp = NULL;
}

 文件缓冲区

系统自动地在内存中为程序中每一个正在使用的文件开辟一块文件缓冲区

介绍:

每当我们想从内存向硬盘中输出数据都会先将数据输送到缓冲区中,然后装满缓冲区后才一起输送到硬盘上。 如果想从硬盘向计算机内读入数据,则会先将读到的数据输送到缓冲区中,装满缓冲区后再逐个将数据输送到程序数据区(内存中的变量)

相当于我们朋友圈的定时发布

我们用以下方法来模拟我们的文件缓存区 

#include 
#include 
int main()
{
	FILE* pf = fopen("data.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
	printf("睡眠10秒-已经写数据了,打开data.txt文件,发现文件没有内容\n");
	Sleep(10000);//延迟函数,单位是毫秒
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
	printf("再睡眠10秒-此时,再次打开data.txt文件有内容了\n");
	Sleep(10000);
	fclose(pf);
	//注:fclose在关闭文件的时候,也会刷新缓冲区
	pf = NULL;
	return 0;
}

一开始我们发现我们的文件是没有放数据的

C语言-文件操作_第59张图片

过了10秒过后

C语言-文件操作_第60张图片


既然我们在写数据的过程中,会让操作系统调用接口来替我们做一些事情,那么写数据这个操作就必然会打断操作系统,如果频繁的写数据,那么操作系统必然会被频繁的打断


为了不会因为频繁的操作而打断操作系统

我们会在内存中另外开辟一块空间

用于存放需要传输的数据,直到缓冲区被放满

再由操作系统一次性全部输送到硬盘中去

可以这么理解:

文件缓冲区在写文件的时候提高整个操作系统的效率,在读文件的时候提高了程序的效率


C语言-文件操作_第61张图片


你可能感兴趣的:(c语言,开发语言)