0


C语言进阶——文件操作

🌳🌲🌱本文已收录至:C语言——梦想系列

更多知识尽在此专栏中!

🎉🎉🎉欢迎点赞、收藏、关注 🎉🎉🎉



🌳前言

** 文件——是我们生活中必不可缺的一部分,优秀的文件管理能使我们工作效率更高,比如上学时的点名册、平时记账的手账本、电脑中存储数据的各种文件夹等。数据构成文件,文件成就数据,因此我们需要学习C语言中的各种文件操作,使数据能够做到持久化存储。**


图片来源:百度图片


🌳正文

文件操作涉及到的内容还是比较多的,大致可分为三个问题:是什么? 怎么用? 要注意什么? 从这三个问题可以衍生出很多问题,其中怎么用是内容最丰富的版块,让我们直接进入正题吧。

🌲关于文件

🌱什么是文件?

如上图所示,这就是文件,不过这是传统的纸质文件。在我们电脑中的都是电子文件夹,存储的都是电子文件,比如数字、图片、文档等,这些数据都是存储在我们电脑的硬盘上的,只要硬盘没坏,那么这些数据随时随地都能找到,而且方便检索。在程序设计中,我们一般将文件分为两种:程序文件数据文件(从文件功能的角度分类),本文主要介绍的是数据文件。

程序文件

包括源程序文件,比如我们的 .c 文件;目标文件,经过预编译、编译、汇编后生成的目标文件,后缀为 .o ,对其进行链接后,就能生成可执行程序;当然最后一种就是可执行程序文件,后缀为.exe

数据文件

就像上图一样,主要存储的是各种数据信息,数据文件的职能是能让程序读取到数据,以及能够对其写入数据,这些数据是能够持久化存储的。

🌱文件有什么用?


电脑C盘中存储的各种信息

文件可以保存数据,使数据能做到持久化存储。文件可以使我们的操作更为合理,比如现在写的这篇博客,本质就是一个文件,不过是存储在服务器上的文件(数据)。电子文件的最大特点就是易于检索了,这也正是电脑的优点之一。至于C语言中的文件可以用于保存程序运行所产生的数据,比如通讯录系统,可以将联系人信息保存到文件中,现在的程序设计数据一般都是存储在数据库中,毕竟本地文件夹安全性还是比较低。

🌱文件的格式是什么?

所有文件都有唯一的标识符,标识符可以分为三部分:文件路径+文件名主干+文件后缀,比如存储在我电脑中的VS文件标识符为:

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE

为了方便称呼,我们一般将其称为文件名,比如 devenv.exe 就是一个典型的程序文件

🌲使用文件

🌱文件指针

文件是一个庞大的集合体,类似于结构体,不过更为复杂,因此在C语言中有一个专门的指针 文件类型指针,简称为 文件指针 用来指向文件首地址。系统会将文件规范化,当使用文件时,系统会在内存中开辟一个对应的文件信息区,这个信息区中包括了文件的各种信息(文件名、文件状态、文件位置等),如果对应信息缺失,系统会自动补齐。前面说过,文件类似于结构体,因此整个文件信息是保存在一个庞大的结构体中的,为了与传统结构体区分开,专门创建了FILE这种特殊的指针,即*文件指针。

因为VS2019将其分的太细了,这里不好演示,但知道FILE** **这个东西本质是个结构体就行了

🌱文件的打开和关闭

文件得先打开,才能关闭,最好跟动态内存管理一样,有申请就要有释放,成对出现更为安全。先来说说打开吧!

🪴文件打开

文件打开用的是** fopen 这个函数,fopen 的作用是从一个文件中以某种方式打开文件,返回类型是 FILE 即打开文件的起始地址,因此我们需要用一个* FILE*** 类型的指针来接收。

**注意:文件打开后,要对文件指针进行判断,如果指针为空,说明文件打开失败,此时要报错,并终止后续操作 **

if (NULL == fp)
{
    //报错函数,说明此文件打开失败
    perror("fopen::test.txt!");
    return 1;//错误结束
}

🪴目标文件

有两种形式,一种是绝对地址,另一种是相对地址

绝对地址

即唯一路径,使用绝对地址访问文件时,文件可以在电脑中的任意位置,前提是地址要合法。绝对位置的文件标识符必须全,即文件路径+文件名主干+文件名后缀。

比如 C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE

注意:

**使用绝对路径时,需要在每个 \ 前额外加一个 \ 原因很简单,单个 \ 与其他字符结合,可能会变成一个转义字符,因此需要两个 \ 来表示一个 \ **

**如果是 Mac 就不需要担心这个问题,因为它用的是 / **

//绝对,指此地址是唯一的,能通过这个地址找到唯一的文件
FILE* fp = fopen(" C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\Common7\\IDE", "w");

相对位置

此时的路径是固定的,一般和当前源文件处于同一位置,相对嘛,就是相对于当前程序文件。相对位置只需要文件名主干+文件后缀就行了 。

比如 devenv.exe ,此时存储位置相对于上面的绝对地址,位于同一目录下

//相对,指在当前工程文件内的文件
FILE* fp = fopen("test.txt", "w");

🪴**打开方式 **

文件打开方式有很多种,比如只读、只写、读+、二进制写等……

值得注意的是当我们通过的方式打开文件时,如果目标文件不存在,那么打开就会失败;但如果是通过的方式打开文件时,如果文件不存在,会自动创建一个目标文件

下面来演示下用写的方式打开文件,然后文件不存在,自动创建文件的情况:

注意:这种是文件的标准使用方式,即先打开,然后判断是否打开失败,如果失败就报错,否则就可以使用文件,最后再关闭文件

//文件创建,通过程序创建
int main()
{
    //可以利用只读的特性:没有就会自己创建
    //这里是相对路径
    FILE* fp = fopen("test.txt", "w");//打开
    if (NULL == fp)
    {
        //报错函数,说明此文件打开失败
        perror("fopen::test.txt!");
        return 1;//错误结束
    }

    //进行文件操作……//

    fclose(fp);//关闭
    fp = NULL;//置空
    return 0;
}

注意:

  • 这个特点很好用,但也很致命,因为每次写文件,都相当于在覆盖文件,假如我们想对原文进行追加,就需要创建原来的数据,再创建新数据,然后一起写入文件中。其实面对这种场景,C语言还提供了另一种文件打开方式 追加 "a",下面就是各种打开指令的集合表。

文件打开指令含义如果目标文件不存在"r" 只读打开一个文件,只能对这个文件进行读取操作打开失败,报错"w" 只写打开一个文件,只能对这个文件进行写入操作建立目标文件"a" 追加向文件末尾处添加数据建立目标文件"rb" 只读打开二进制文件,只能对其进行二进制读取打开失败,报错"wb" 只写打开二进制文件,只能对其进行二进制写入建立目标文件"ab" 追加向二进制文件末尾处添加数据追加失败,报错"r+" 读写打开一个文件,可以进行读取和写入操作打开失败,报错"w+" 读写打开一个文件,可以进行读取和写入操作建立目标文件"a+" 读写对文件末尾处进行读取和写入操作建立目标文件"rb+" 读写打开二进制文件,可以进行二进制读取和写入打开失败,报错"wb+" 读写打开二进制文件,可以进行二进制读取和写入建立目标文件"ab+" 读写对二进制文件末尾处进行读取和写入操作建立目标文件
🪴文件关闭

文件关闭用到的是** fclose** 这个函数,fclose 就比较简单了,只需要一个参数—文件指针(FILE),然后就能关闭这个文件指针所指向的文件,感觉有点像* free,功能强大,使用方便。同 free 一样,fclose** 关闭文件后,也需要将指针(文件指针)置空,避免出现野指针

fclose(fp);//关闭
fp = NULL;//置空

🌲顺序读写

🌱输入、输出流

在介绍文件读写操作前,需要先说明一下C语言中“流”(format)的概念。假设将数据看作水流,那么它就有两个关键部分:从哪里流出(源头)、流入哪里(终点),其中流出可以看作输出流入可以看作输入。C语言中有三种流:标准输入输出流、文件输入输出流、二进制输入输出流(实际使用时用前两种流,第三种的目标流一般为文件)。

🪴标准输入输出流

标准输入输出流(I/O)包括标准输入流(stdin)—从键盘输入标准输出流(stdout)—从屏幕输出标准错误流(strerr)—从屏幕输出,任何一个C程序,只要运行起来都会默认打开以上三个流,比如我们常用的** scanf、printf就是基于标准输入输出流而运行的**,这也正是二者需要键盘、屏幕的原因。

🪴文件输入输出流

顾名思义,文件输入输出流所依赖的载体为文件,无论是输入还是输出数据,都是在文件上进行的,因此它的对象类型为FILE* ,文件输入输出流可以使用所有输入输出流函数,比如fputc、fprintf、fscanf等,使用时只需要加上目标流类型就行了。

🪴二进制输入输出流

二进制输入输出流主要适用于文件操作,对文件进行二进制数据的读取和写入,所以二进制输入输出一般用在文件操作中。二进制只有0、1这两个数,因此如果我们使用二进制输出流对某个文件进行写入,文件中存储的信息就变成了一串二进制数(可以使用二进制文件查看器观察),如果用普通文本的形式查看此文件,会得到一串乱码。二进制输入输出流有fwrite、fread这两个函数。

注意:

  • printf、scanf、gets等这种不需要指定目标流的函数,设计时就已经规定好了,它们是标准输入输出流函数。而fprintf、fscanf、fgets等这些面向所有输入输出流的函数更为原始,需要用户使用时根据具体情况选择目标流,所以这些函数也能实现标准输入输出流函数的功能,只需要把目标流写成 stdin(输入)、stdout(输出)就行了。下面是各种输入输出函数的集合表:

功能函数名适用于(目标流)进行单字符的输入fgetc 所有输入流 进行单字符的输出fputc所有输出流文本行输入函数(读取一行数据)fgets所有输入流文本行输出函数(写入一行数据)fputs所有输出流格式化输入函数fscanf所有输入流格式化输出函数fprintf所有输出流二进制输入函数fread文件输入流二进制输出函数fwrite文件输出流

注意:为了方便函数的介绍,接下来会先介绍写入(输出),再介绍读取(输入)函数

🌱fputc 与 fgetc

** fputc 对文件进行单字符的写入fgetc **读取文件中的单字符

🪴fputc

//文件读写之逐字符写
//因为没有数据,所以我们先写再读
int main()
{
    //打开
    FILE* fp = fopen("test.txt", "w");
    if (NULL == fp)
    {
        perror("fopen::test.txt!");
        return 1;
    }

    //进行操作
    char* pc = "abcdef123";
    //逐字符写入
    while (*pc)
    {
        fputc(*pc, fp);//逐字符放
        pc++;
    }

    //关闭
    fclose(fp);
    fp = NULL;
    return 0;
}

🪴fgetc

//文件读写之逐字符读
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror("fopen::test.txt!");
        return 1;
    }
    //逐字符读取
    int ch = 0;//需要用整型,因为EOF是-1
    while ((ch = fgetc(fp)) != EOF)
    {
        //逐字符读取后,赋给字符变量ch,然后打印
        printf("%c", ch);
    }

    //关闭
    fclose(fp);
    fp = NULL;
    return 0;
}

**注意: **

  • 在读取或写入字符串时,可以通过特定的条件结束读写。比如写入:可以通过字符串自带的结束标志 \0 结束写入读取:可以通过fgetc的返回值进行判读,如果返回 -1(EOF) 就说明数据已经读取完了
  • *单纯写文本数据时,要使用指令 "w" 单纯读数据时,要使用指令 "r" 指令与操作一定要匹配上*,不然就会发生意想不到的错误

🌱fputs 与 fgets

** fputs** 对文件进行一行数据的写入fgets读取文件中的一行数据

🪴fputs

//文件读写之行写
int main()
{
    FILE* fp = fopen("test.txt", "w");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }

    char* pc = "这是由标准输入输出流写入的数据";
    fputs(pc, fp);//行写入
    fclose(fp);
    fp = NULL;
    return 0;
}

🪴fgets

//文件读写之行读
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }

    char tmp[30] = "0";
    fgets(tmp, sizeof(tmp), fp);//行读取
    printf("%s\n", tmp);
    fclose(fp);
    fp = NULL;
    return 0;
}

注意:

  • *行写入时,要确保写入的是字符串数据,传参数时要传地址;行读取时,需要设定待读取数据的数量,一般是跟待存储空间大小相匹配。如果行读取结束,有两种情况:***1、因无法读取数据而结束 **** ****2、因读取到文件末尾而结束 **
  • 单纯文本数据时,要使用指令** "w"**** ;单纯数据时,要使用指令 ****"r" **

🌱fprintf 与 fscanf

** fprintf** 是对文本进行格式化数据的写入fscanf 是将文本中的数据进行格式化读取

🪴fprintf

//按照文件流格式化写入
struct S
{
    char name[20];
    int age;
    float score;
}a = { "张三",20,88.8f };
int main()
{
    FILE* fp = fopen("test.txt", "w");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    fprintf(fp, "%s %d %.2f", a.name, a.age, a.score);//适用于所有流的格式化输入输出函数

    fclose(fp);
    fp = NULL;
    return 0;
}

🪴fscanf

//按照文件流格式化读取
struct S
{
    char name[20];
    int age;
    float score;
}tmp;
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    fscanf(fp, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));//适用于所有流的格式化输入输出函数
    printf("%s %d %.2f\n", tmp.name, tmp.age, tmp.score);
    fclose(fp);
    fp = NULL;
    return 0;
}

🪴延申:sprintf 和 scanf

除了**fprintf / fscanf **和 printf / scanf 这两组格式化输入输出外,还存在另一组格式化输入输出函数:sprintf / sscanf

简单介绍一下,sprintf****是把格式化的数据按照一定的格式转换为字符串,相反的,**sscanf **就是从字符串中按照一定格式读取出格式化的数据

sprintf 和** sscanf 可以把结构体中的数据打包成一个字符串,也可以对某个字符串进行拆分。这个东西在我们生活中有应用,比如当我们登录账号时,会把账号、密码这个结构体打包成一串字符串,交给后端处理,当然有个更高级的名词:序列化与反序列化**。

注意:

  • *printf 输出家族返回的是*实际写入(输出)的字符总数(包括转义字符),而 scanf 输入家族返回的是实际读取(输入)的元素个数*。举个栗子,字符串 abc ,输出返回 3,输入返回 1,因为此时的字符串视为一个元素。*
  • *单纯文本数据时,要使用指令 "w" ;单纯*数据时,要使用指令 ****"r" **

🌱fwrite 与 fread

** fwrite** 是对文件进行二进制数据的写入fread 是以二进制的形式读取文件中的数据

🪴fwrite

//文件读写之二进制写入
struct S
{
    char name[20];
    int age;
    float score;
}a = { "张三",20,88.8f };

int main()
{
    //把a中的数据写到文件中
    FILE* fp = fopen("test.txt", "wb");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    //二进制的写文件
    fwrite(&a, sizeof(a), 1, fp);

    fclose(fp);
    fp = NULL;
    return 0;
}

🪴fread

//文件读写之二进制读取
struct S
{
    char name[20];
    int age;
    float score;
}tmp;

int main()
{
    //把文件中的数据读取到tmp中
    FILE* fp = fopen("test.txt", "rb");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    fread(&tmp, sizeof(tmp), 1, fp);
    printf("%s %d %.2f\n", tmp.name, tmp.age, tmp.score);

    fclose(fp);
    fp = NULL;
    return 0;
}

注意:

  • 当我们使用二进制写入数据到文件时,如果是以文本的方式打开,只能看懂字符串部分,数字部分是看不懂的,我们可以通过VS中的二进制编辑器,来观察其中的数据。
  • 单纯二进制数据时,要使用指令 "wb" ;单纯二进制数据时,要使用指令** "rb" **

🌲随机读写

随机读写函数,需要配合上面的输入输出函数使用,所谓的随机读写,是指通过改变文件指针的偏移量,来写入或读取数据。介绍三个和随机读取有关的函数:fseek 改变文件指针偏移量ftell 查看当前文件指针的偏移量rewind 使文件指针复原至起始位置

🌱fseek

//fseek,文件指针偏移量
int main()
{
    FILE* fp = fopen("test.txt", "w");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    char* pc = "abc";
    fseek(fp, 20, SEEK_SET);//从起点往后偏移
    fputs(pc, fp);

    fclose(fp);
    fp = NULL;
    return 0;
}

🌱ftell

//ftell,返回当前文件指针偏移信息
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror(fp);
        return 1;
    }
    printf("当前文件指针偏移量为:%d\n", ftell(fp));
    fseek(fp, 20, SEEK_SET);//向后偏移20
    printf("经过fseek设置后的文件指针偏移量为:%d\n", ftell(fp));
    fclose(fp);
    fp = NULL;
    return 0;
}

🌱**rewind **

//rewind,使文件指针恢复至原位置
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    fseek(fp, 20, SEEK_SET);//先让文件指针向后偏移20
    printf("当前文件指针偏移量为:%d\n", ftell(fp));
    rewind(fp);//使文件指针恢复至起始位置
    printf("经过恢复后的文件指针偏移量为:%d\n", ftell(fp));
    return 0;
}

🌱fseek、ftell、rewind 三合一

//fseek、ftell、rewind三合一
//假设文件中存储数据为abcdef
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    printf("现在文件中内容为abcdef,我们要依次取出e、b、d\n");
    fseek(fp, -2, SEEK_END);//从后往前偏移
    printf("先取出字符%c\n", fgetc(fp));
    rewind(fp);//还原至起始位置
    fseek(fp, 1, SEEK_SET);//从前往后偏移
    printf("再取出字符%c\n", fgetc(fp));
    fseek(fp, 1, SEEK_CUR);//从当前位置向后偏移
    printf("最后再取出字符%c\n", fgetc(fp));

    fclose(fp);
    fp = NULL;
    return 0;
}

注意:

  • 每进行一次文件输入输出操作,文件指针都会向后移动一位。比如上面的三合一, 当我们读取到字符 'b' 后,文件指针向后移动一位,指向字符 'c' ,此时只需要把文件指针向后偏移一位****,就能愉快的读取到字符 'd' 了。

🌲文本文件与二进制文件

🌱文本文件

文本文件指以ASCII码(文本方式)存储的数据,原始数据机器能直接看懂,将内存中的数据对应ASCII码解码存储后,我们人类也能看懂,举个栗子,在记事本中写的文本,就是文本文件

🌱二进制文件

二进制文件是将数据编译后转成二进制形式,然后直接存储的文件,这种文件机器能秒懂,读取效率很高(因为不需要转译),但二进制一般人是看不懂的,部分二进制数据也无法通过ASCII码解码为正确的数据,因此强行输出二进制文件,极有可能会得到乱码。比如将上面的那段话通过二进制形式写入文件中,可以看到除字符类型数外,其他类型的数据变成了乱码。

下图为上面的二进制文件在内存中以二进制形式存储的样子,显示为十六进制(节省空间),实际为二进制

🌱注意

  • 如果待读取的文件中存储的是二进制数据,就需要使用 二进制读取 "rb" 的形式读取数据;反之如果想写入二进制数据,就需要用 二进制写入 "wb" ,无论是二进制还是普通文本,计算机都能读懂,只是我们看不得罢了。小技巧:可以使用二进制存储重要数据,这样外行人一时半会也理解不了。

🌲文件使用注意事项

🌱被错误使用的feof

很多人在写C语言课设的时候(学生信息管理系统、通讯录系统等),会通过 feof****来判断文件是否读取结束,这是一种错误的用法,因为** feof** 的作用是判断当前文件读取结束原因的,如果是因为读取到了末尾而结束,**feof(fp) 就为真;除了这个以外,还有另一个文件读取结算原因判断函数,ferror 当**** ferror(fp) *为真时,说明此时发生了读取异常,并非正常结束,我们可以通过这两个报错函数来判断文件读取结束的真正原因。*

    char arr[100] = "0";
    fgets(arr, sizeof(arr), fp);
    printf("%s\n", arr);
    int n = 0;
    if ((n = feof(fp)))
        printf("End by EOF\n");
    if (ferror(fp))
        printf("End by IO Error\n");

🌱文件读取结束原因判断

既然** feof** 不是用来判断读取是否结束的,那说明存在其他判断方法,其实答案就是函数设计中,前辈在设计函数时已经考虑好了,比如** fgetc** 没有读取到数据会返回EOF,fgets 没有读取到数据会返回NULL,fscanf 可以通过其返回的实际读取元素个数进行判断,fread 可以通过返回值与指定读取的元素数比较。每种读取函数都有属于的自己的判断方法,比如下面这两个例子:

🪴对文本数据进行读取

//读取错误信息判断
//1.文本文件版,假设文件内已有信息,为abcdef
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }

    int ch = 0;//接收读取的数据,要用整型,因为EOF为-1
    while ((ch = fgetc(fp)) != EOF)
    {
        printf("%c", ch);
    }
    //判断是为何结束
    if (feof(fp))
        printf("\nEnd by EOF(因读到文件末尾而结束)\n");
    else if (ferror(fp))
        printf("\nEnd by IO(因中途读取失败而结束)\n");

    fclose(fp);
    fp = NULL;
    return 0;
}

🪴对二进制文件进行读取

//2.二进制文件版
enum { SIZE = 5 };//相当于宏定义
int main()
{
    double a[SIZE] = { 1.1,2.2,3.3,4.4,5.5 };
    //首先把五个浮点数以二进制的形式,写入文件中
    FILE* fp = fopen("testFoBin.txt", "wb");
    if (NULL == fp)
    {
        perror(fp);
        return 1;
    }
    fwrite(&a, sizeof(*a), SIZE, fp);
    fclose(fp);//写入完成,关闭文件

    double b[SIZE] = { 0.0 };
    //现在以二进制的形式读取数据
    fp = fopen("testFoBin.txt", "rb");
    int size = fread(&b, sizeof(*b), SIZE, fp);
    if (size == SIZE)
    {
        printf("No Error!\n");
        int i = 0;
        while (i < size)
            printf("%.2lf ", b[i++]);
    }
    else
    {
        if (feof(fp))
            printf("\nError by EOF\n");
        else if(ferror(fp))
            printf("\nError by IO\n");
    }
    fclose(fp);
    fp = NULL;
    return 0;
}

🌱文件缓冲区

ANSIC 标准定义了“缓冲文件系统”这个概念,所谓缓冲文件系统是指系统自动地在内存中为程序
中每一个正在使用的文件开辟一块“文件缓冲区”。无论是读取还是写入数据时,都会先将数据送入文件缓冲区,等文件缓冲区****装满遇到刷新指令后,数据才会被读取(写入)到目标空间中。文件缓冲区的大小是由编译器决定的。

🪴验证文件缓冲区是否存在

我们可以利用睡眠函数** Sleep** 来使程序暂停,此时数据还没有被写入文件中,仍然位于缓冲区;之后再手动刷新缓冲区,数据此时会被推送至文件中

//文件缓冲区
#include<windows.h>
int main()
{
    //打开文件
    FILE* fp = fopen("test.txt", "w");
    if (NULL == fp)
    {
        perror(fp);
        return 1;
    }
    char* ps = "测试文件缓冲区";
    fputs(ps, fp);//先将数据写到缓冲区中
    printf("数据现在已经在缓冲区里面了,但还没有推送到文件中\n");
    printf("程序睡眠10秒,10秒后刷新缓冲区\n");
    Sleep(10000);//睡眠函数,单位是毫秒
    fflush(fp);
    printf("现在缓冲区已经刷新,数据已经写入文件中了\n");
    Sleep(10000);

    //关闭文件,当文件关闭时,缓冲区也会被刷新
    fclose(fp);
    fp = NULL;
    return 0;
}

** 可以看到文件缓冲区是真实存在的。**

注意:

  • fclose 关闭文件后,会自动刷新缓冲区,数据能够推送至文件
  • 当程序运行结束后,缓冲区也会被自动刷新
  • scanf 遇到 \n 也会触发缓冲区刷新,另外如果其在读取字符型数据时,遇到空白字符(空格、TAB键)也会触发缓冲区的刷新

🌳总结

** 以上就是C语言文件操作的所有内容了,从文件的打开到文件的关闭,中间可以进行多种操作,构造出巧妙的数据。当然前提是我们得学会文件的相关操作,可以巧记为单字符读写、行读写、格式化读写和二进制读写,无论是那种操作,都需要和对应的文件操作指令匹配上;关于随机读写,记住那三个偏移量函数就行了;最后需要对文件缓冲区有一定的理解,确保数据能成功推送至文件内。总之,文件操作的学习可以宣布毕业了。**

** 如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!**

** 如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正**

相关文章推荐

C语言进阶——动态内存管理

C语言进阶——自定义类型

C语言进阶——指针进阶试题讲解(万字长文详解)

标签: c语言 c++ 学习

本文转载自: https://blog.csdn.net/weixin_61437787/article/details/127271850
版权归原作者 Yohifo 所有, 如有侵权,请联系我们删除。

“C语言进阶&mdash;&mdash;文件操作”的评论:

还没有评论