C语言——文件操作

首先,C语言是可以操作文件的,虽然并不是很常用

1,为什么使用文件

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

2,什么是文件

我们一般谈的文件按功能分类有两种:程序文件,数据文件

程序⽂件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows 环境后缀为.exe)

⽂件的内容不⼀定是程序,⽽是程序运⾏时读写的数据,⽐如程序运⾏需要从中读取数据的⽂件,或 者输出内容的⽂件。这样的文件就是数据文件。

一般的我们处理数据的输⼊输出都是以终端为对象的,即从终端的键盘输⼊数据,运⾏结果显示到显示器上。这也称为标准输入输出。

但有时我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使⽤,这⾥处 理的就是磁盘上⽂件。

⼀个⽂件要有⼀个唯⼀的文件标识,以便用户识别和引⽤。

⽂件名包含3部分:⽂件路径+⽂件名主⼲+⽂件后缀

例如: c:\code\test.txt 。为了⽅便起见,⽂件标识常被称为⽂件名。

下面我们讨论的主要都是数据文件。

3,二进制文件和文本文件

根据数据的组织形式,数据⽂件被分为**⽂本⽂件⼆进制⽂件**。

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

还有一种判断方法,打开一个文件,你看得懂的就是文本文件,看不懂的就是二进制文件:

左边就是文本文件,右边就是二进制文件

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

字符⼀律以ASCII形式存储,数值型数据既可以⽤ASCII形式存储,也可以使⽤⼆进制形式存储

如整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占⽤5个字节(每个字符⼀个字节),⽽⼆进制形式输出,则在磁盘上只占4个字节。

4,文件的打开和关闭

4.1,流和标准流

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出 操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流 想象成流淌着字符的河。C程序针对⽂件、画面、键盘等的数据输⼊输出操作都是通过流操作的。 ⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。

一般读写文件时,都是1,先打开和文件相关的流,2,再读写文件,3,最后关闭和文件相关的流

C语⾔程序在启动的时候,默认打开了3个标准流:

• stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。

• stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出 流中。

• stderr - 标准错误流,⼤多数环境中输出到显⽰器界⾯。

使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作就是因为默认打开了这三个流。

stdin、stdout、stderr 三个流的类型是: FILE *,通常称为⽂件指针。 C语⾔中,就是通过 FILE* 的⽂件指针来维护流的各种操作的。

4.2,文件指针

缓冲⽂件系统中,关键的概念是"⽂件类型指针",简称"⽂件指针"。

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

不同的C编译器的FILE类型包含的内容不完全相同,但是⼤同⼩异。

每当打开⼀个⽂件的时候,系统会根据⽂件的情况⾃动创建⼀个FILE结构的变量,并填充其中的信 息,使⽤者不必关⼼细节。 ⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便。

VS2022中FILE的定义如下:

cpp 复制代码
    #define _FILE_DEFINED
    typedef struct _iobuf
    {
        void* _Placeholder;
    } FILE;

4.3,文件的打开和关闭

⽂件在读写之前应该先打开⽂件,在使⽤结束之后应该关闭⽂件。 在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了 指针和⽂件的关系。

ANSI C 规定使⽤ fopen 函数来打开⽂件, fclose 来关闭⽂件。

fopen打开文件失败时会返回空指针NULL

mode表示文件打开模式,有:

|-------------|--------------------------|---------------|
| ⽂件使⽤⽅式 | 含义 | 如果指定⽂件不存在 |
| "r"(只读) | 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 | 出错 |
| "w"(只写) | 为了输出数据,打开⼀个⽂本⽂件 | 建⽴⼀个新的⽂件 |
| "a"(追加) | 向⽂本⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
| "rb"(只读) | 为了输⼊数据,打开⼀个⼆进制⽂件 | 出错 |
| "wb"(只写) | 为了输出数据,打开⼀个⼆进制⽂件 | 建⽴⼀个新的⽂件 |
| "ab"(追加) | 向⼀个⼆进制⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
| "r+"(读写) | 为了读和写,打开⼀个⽂本⽂件 | 出错 |
| "w+"(读写) | 为了读和写,建议⼀个新的⽂件 | 建⽴⼀个新的⽂件 |
| "a+"(读写) | 打开⼀个⽂件,在⽂件尾进⾏读写 | 建⽴⼀个新的⽂件 |
| "rb+"(读写) | 为了读和写打开⼀个⼆进制⽂件 | 出错 |
| "wb+"(读 写) | 为了读和写,新建⼀个新的⼆进制⽂件 | 建⽴⼀个新的⽂件 |
| "ab+"(读 写) | 打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写 | 建⽴⼀个新的⽂件 |

补充:以写的形式打开文件,如果文件存在,会清空文件

当前目录下没有该文件,就报错了

使用写的方式打开就没有报错了,而且在当前目录下给我创建了一个文件

不仅可以打开当前目录下的,还可以打开其它目录下的,就是用绝对路径,相对路径

什么是绝对路径,什么是相对路径,绝对路径和相对路径的区别?-CSDN博客

至于 / 和 \ ,在C语言里都可以,只不过 使用 \ 要注意转义字符

5,文件的顺序读取

顺序读取函数有:

|-------------|-------------|-----------|
| 函数名 | 功能 | 适⽤于 |
| fgetc | 字符输⼊函数 | 所有输⼊流 |
| fputc | 字符输出函数 | 所有输出流 |
| fgets | ⽂本⾏输⼊函数 | 所有输⼊流 |
| fputs | ⽂本⾏输出函数 | 所有输出流 |
| fscanf | 格式化输⼊函数 | 所有输⼊流 |
| fprintf | 格式化输出函数 | 所有输出流 |
| fread | ⼆进制输⼊ | ⽂件输⼊流 |
| fwrite | ⼆进制输出 | ⽂件输出流 |

上⾯说的适⽤于所有输⼊流⼀般指适⽤于标准输⼊流和其他输⼊流(如⽂件输⼊流);所有输出流⼀ 般指适⽤于标准输出流和其他输出流(如⽂件输出流)。

5.1,fputc和fgetc

fputc把字符写到文件中,如图:,为什么可以把这些字母全部写到文件里呢而不会覆盖呢,因为它们是顺序读取函数,每写入一个字符后,光标都会向后移动一位,多所以就能够全部写入并全部读取了,

注意,fgetc的返回值如果读取成功就返回读取到的字符,读取失败时会返回EOF,也就是-1,所以返回值类型是int,如果用char,EOF就无法接收。所以用int更好。

因为是针对所用输入输出流,所以也可以从键盘上读取,再写到显示屏上

5.2,fputs和fgets

fputs,和fgets都是读取一行

虽然fputs是读取一行,但是使用多个fputs写到文件中并不会换行,要换行还要自己加上\n

而fgets是从文件中读取,把它存储到一个字符数组中,num是最大读取个数

当num没有文件中字符多时,就读取num个字符,但要注意,这num个字符中包括了\0,

所以实际读取的字符个数是num-1个,

num大于这一行字符个数时,就把这一行全部读取出,末尾再加一个\0

fgets一次读取一行,第二次调用fgets读取同一个文件才读取第二行

同样的,fgets和fputs也可以从标准输入输出流中读取,这里就不再演示了

5.3,fscanf和fprintf

可以发现,fprintf和printf的参数很像,所以使用fprintf时就把它想成printf,只不过在前面加一个文件指针,当然,如果这个指针是stdout,那么就是打印到显示屏上,

fscanf也与scanf很像,就不再赘述了

5.4,fread和fwrite

前面的函数都是读写到文本文件中,而接下来的两个是读写到二进制文件中,

fwrite就是把ptr指向的内容中count个大小为size的数据写到文件中,fread就刚好相反

5.5,对比:scanf fscanf sscanf , printf fprintf sprintf

scanf是针对stdin的格式化输入函数

printf是针对stdout的格式化输出函数

fscanf是针对所有输入流的输入函数

fprintf是针对所有输出流的输出函数

sscanf是从字符串中格式化提取数据

sprintf是从把数据格式化转化成字符串

6,文件的随机读取

上面的顺序读取是一个一个向后读取,那有没有什么办法能够跳跃式的读取,想读哪里就读取哪里,这就是文件的随机读取了。

6.1,fseek

根据⽂件指针的位置和偏移量来定位⽂件指针(⽂件内容的光标)

第二个参数是偏移量,第三个参数是源头即参考点,即相对于参考点的偏移量

origin有三种取值,分别是文件开头,光标当前位置,和文件末尾

顺序读取是读取到了ab,

那如果我们想第二个读取e,该怎么做呢。我们就使用fseek来修改光标的位置,e相对于文件开头的偏移量是4,可以理解为数组下标

可以看到此时就能够读取到e了。

同样的使用另外两种origin也可以达到这样的效果

要注意使用SEEK_END时,是向左偏移,偏移量就是负的

6.2,ftell

返回⽂件指针相对于起始位置的偏移量

还是这个例子,e的偏移量是4,读完e后指针后移,此时偏移量就是5了

6.3,rewind

让⽂件指针的位置回到⽂件的起始位置

rewind之后光标就回到了起始位置,再去读取就又从第一个开始读了

7,文件读取结束的判定

文件读取结束有两种情况,一种是正常的读取到了文件末尾,另一种是读取时发生错误导致读取结束

文件读取结束后,要判断文件是那种原因导致读取结束就要用到两个函数------feof,ferror。

我们知道EOF是文件读取结束的的标志,但feof使用时是来判断文件读取时是否遇到了文件末尾

ferror是来判断是否是发生了错误而导致文件读取结束。

文件读取时都会设置标记1/0,来判断是否遇到了文件结尾(1),以及是否发生了错误(1),注意是两个不同的标记,分别判断。

那么如何判断文件是否读取结束呢?

这就要利用到读取文件的函数的返回值了,

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

fgetc 判断是否为 EOF.

fgets 判断返回值是否为 NULL .

  1. ⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。 例如:

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

这就是读取到文件末尾了

这是因为以写到方式打开却偏要去读,所以就发生错误了

8,文件缓冲区

ANSI C 标准采⽤"缓冲⽂件系统" 处理数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程 序中每⼀个正在使⽤的⽂件开辟⼀块"⽂件缓冲区"。从内存向磁盘输出数据会先送到内存中的缓冲 区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊ 到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲 区的大小根据C编译系统决定的。

从缓冲区写到硬盘上有三种情况:

1,写满缓冲区

2,主动刷新缓冲区

3,fclose------刷新缓冲区

验证缓冲区代码:

cpp 复制代码
#include <stdio.h>
#include <windows.h>
//VS2022 WIN11环境测试
int main()
{
    FILE*pf = fopen("test.txt", "w");
    fputs("abcdef", pf);//先将代码放在输出缓冲区
    printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
    Sleep(10000);
    printf("刷新缓冲区\n");
    fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
    //注:fflush 在⾼版本的VS上不能使⽤了
    printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
    Sleep(10000);
    fclose(pf);
    //注:fclose在关闭⽂件的时候,也会刷新缓冲区
    pf = NULL;
    return 0;
}
相关推荐
R6bandito_39 分钟前
Qt几何数据类型:QLine类型详解(基础向)
c语言·开发语言·c++·经验分享·qt
禊月初三44 分钟前
C++面试突破---C/C++基础
c语言·c++·面试
就爱学编程1 小时前
重生之我在异世界学编程之C语言:选择结构与循环结构篇
c语言·数据结构·算法
橘子真甜~1 小时前
Linux操作系统3-文件与IO操作1(从C语言IO操作到系统调用)
linux·运维·服务器·c语言·io·文件操作·文件fd
Lbs_gemini06038 小时前
C++研发笔记14——C语言程序设计初阶学习笔记12
c语言·开发语言·c++·笔记·学习
没有名字的鬼14 小时前
C_字符串的一些函数
c语言·开发语言·算法
Heris9914 小时前
零基础快速掌握——c语言基础【二维数组】
c语言·开发语言·数据结构·算法
tjsoft15 小时前
delphi 12 idhttpsever(S)+idhttp(C) 实现简单的JSON API服务
c语言·开发语言·json
武昌库里写JAVA15 小时前
SpringBoot
c语言·开发语言·数据结构·算法·二维数组
Ning_.16 小时前
C语言链表分区问题
c语言·网络·链表