
(文件操作)

大家好,很高兴又和大家见面啦!!!
在之前的文章中,我们已经对“文件”有了比较全面的认识:
我们了解了文件的基本操作,如打开、读取、写入和关闭,也区分了文本文件与二进制文件的本质不同:
理解了“什么是文件”以及“文件如何存储”之后,我们自然会问:在C语言中,如何具体实现文件的操作?
这就涉及到一个核心概念——“流”(Stream),以及与之相关的文件指针和标准流机制。通过“流”,C语言将不同的输入输出设备(如键盘、屏幕、磁盘文件)抽象为统一的数据通道,使得文件操作变得灵活而一致。
现在,就让我们一起深入探讨C语言中的“流”与文件操作的具体实现。
流是一个内涵非常丰富的概念,它既指液体或气体这类物质的移动,也是计算机科学中用于抽象描述数据序列或传输过程的重要模型。
在物理世界中,“流”最直观的理解就是液体或气体的移动。例如,河水的流动、空气的流动(风)都是一种流。流体力学就是研究流体运动规律的科学。其核心原理包括:
在计算机科学中,流(Stream) 是一个非常重要且基础的抽象概念,它核心是数据处理的一种方式。它主要用来处理一系列有序的数据元素。
如何理解这个抽象概念呢?
在成长的过程中,我相信大家都有听过一句话——我们遨游在知识的海洋中。
在这句话中,我们就把所学习的各种知识给具象化成了组成海洋的海水,由这些海水汇聚而形成的海洋,我们就将其称之为知识的海洋。
同样的,在计算机邻域中,我们把计算机中的各种数据具象化成河水,这些河水会汇聚成一条河流,不断地在计算机世界中进行流动。
我们可以向这条河流中倒入新的河水(数据),同样的我们也可以从这条河流中取出我们需要的河水(数据)。
但是有一点需要注意:我们不管是倒入新的河水,还是取出我们需要的河水,我们首先需要做的就是找到这条河流,即,我们在进行输入数据和输出数据前,需要先打开流。
那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语⾔程序在启动的时候,默认打开了3个流:
stdin标准输⼊流:在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。stdout标准输出流:⼤多数的环境中输出⾄显示器界⾯,printf函数就是将信息输出到标准输出流中stderr标准错误流:⼤多数环境中输出到显示器界⾯。这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。
stdin、stdout、stderr三个流的类型是: FILE*,通常称为文件指针。
C语⾔中,就是通过 FILE* 的文件指针来维护流的各种操作的。
缓冲⽂件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使⽤的文件都在内存中开辟了⼀个相应的文件信息区,⽤来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名FILE。
例如,VS2013编译环境提供的 stdio.h 头⽂件中有以下的⽂件类型申明:
struct _iobuf {
char* _ptr;
int _cnt;
char* _base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char* _tmpfname;
};
typedef struct _iobuf FILE;不同的C编译器的 FILE 类型包含的内容不完全相同,但是大同小异。
每当打开⼀个⽂件的时候,系统会根据⽂件的情况⾃动创建⼀个FILE 结构的变量,并填充其中的信息,使用者不必关心细节。
⼀般都是通过⼀个 FILE 的指针来维护这个 FILE 结构的变量,这样使⽤起来更加⽅便。
下⾯我们可以创建⼀个 FILE* 的指针变量:
FILE* pf = NULL; // 创建文件指针变量
// pf——point file:指向文件的指针这里我们定义了一个指向 FILE 类型数据的指针变量 pf。
graph TB
pf[pf]可以使 pf 指向某个文件的文件信息区(⼀个结构体变量)。
graph LR
pf--->file[文件信息区]通过该⽂件信息区中的信息就能够访问该⽂件。
graph LR
pf --->file[文件信息区]--->File[文件]也就是说,通过文件指针变量能够间接找到与它关联的⽂件。
在前面的内容中我们就有介绍过,我们可以对文件执行的基本操作有:创建、销毁、读取、写入……
因此我们首先需要学习的基本操作就是文件的打开与关闭。
在C语言的 stdio.h 这个头文件中,有一系列专门用来进行文件操作的库函数:
fclose()——用于关闭一个已经打开的流(文件)fflush()——用于强制将输出流或更新(最近操作为输出)缓冲区中的数据立即写入文件fopen()——用于使用指定模式打开一个文件,并关联一个流freopen()——用于将已有的流重新关联到另一个文件setbuf()——用于为指定的流设置自定义缓冲区或禁用缓冲setvbuf()——用于为指定的流设置缓冲模式(全缓冲、行缓冲、无缓冲)和缓冲区下面我们主要介绍两个函数——fopen 与 fclose
fopen()在 cplusplus.com 网站中有对该函数的详细介绍,有兴趣的朋友可以点击链接进行访问。这里我们对该函数进行一下使用说明:

从上述的函数说明中我们可看到函数的一个大致用法:
那么该函数能够对文件进行哪些具体的操作呢?

简单来说,核心操作有3类:
在这三类核心操作中,我们可以细分为以下操作:
r :只读操作。为了输入数据,打开一个已经存在的文本文件rb:只读操作。为了输入数据,打开一个二进制文件r+:读写操作。为了读和写,打开一个文本文件rb+:读写操作。为了读和写,打开一个二进制文件w:只写操作。为了输出数据,打开一个文本文件wb:只写操作。为了输出数据,打开一个二进制文件w+:读写操作。为了读和写,建立一个新的文件wb+:读写操作。为了读和写,建立一个新的二进制文件a:追加操作。向文本文件末尾添加数据ab:追加操作。向二进制文件末尾添加数据a+:读写操作。在文件末尾进行读写ab+:读写操作。在二进制文件末尾进行读写fopen 这个函数对给定的操作是否成功,会有相应返回值:
errno 变量设置为系统特定的错误代码fclose()
该函数的用法比较简单:
如果关闭成功,函数会返回 0 ,如果关闭失败,函数会返回 EOF。并且即使函数关闭失败,作为参数的 FILE* 指针指向的文件所关联的流也会与该文件和缓冲区断开关联。
了解了这两个函数的基本用法,下面我们就可以来尝试着使用一下这两个函数了:
void test1() {
FILE* pf = NULL; // 创建文件指针变量
// pf——point file:指向文件的指针
// 这里我采用w+模式来创建一个名为:text.txt的文本文件,并进行读写
pf = fopen("text.txt", "w+");
// 当文件打开成功
if (pf) {
// 通过文件输入函数,向文件中写入:"fopen test"
fputs("fopen test", pf);
// 完成写入操作后,关闭流
fclose(pf);
}
}下面我们就来测试一下:

此时程序并没有报错,那就说明我们成功创建了这个文件,并对其进行了写操作。由于我们并未对该文件的创建指定路径,因此文件会自动创建在与程序相同的文件夹下:

可以看到此时我们已经成功创建了这个文件,并且文件名为我们指定的命名。当我们打开该 .txt 文件后,我们就可以查看是否写入成功:

可以看到,文件中已经写入了我们需要写入的内容。
那我们如何指定文件的路径呢?
很简单,我们只需要在文件名的前面加上其所在路径即可:

从测试结果中我们可以看到,我们最开始通过仅读模式在指定路径中打开该文件时,由于不存在这个文件,因此返回空指针;
从 perror 的报错中我们也能获取相关信息,之后我们再一次通过仅写模式,成功在指定路径下创建了该文件,并将指定内容写入了文件内。
今天的内容到这里就全部结束了。至此,我们已经对C语言文件操作的核心基础进行了一次深入的探索。让我们一同回顾本次旅程的要点:
stdin(标准输入)、stdout(标准输出)和 stderr(标准错误),这正是 scanf 和 printf 等函数能直接工作的原因。
fopen 和 fclose 这一对必须成对使用的函数,详细了解了各种文件打开模式(如 r, w, a, r+ 等)的区别与适用场景,并成功进行了实践。
掌握文件的打开与关闭,只是迈入了文件处理世界的第一步。接下来,我们将学习如何具体地对文件进行读取和写入,探索更多强大的文件操作函数。
如果这篇博客帮助您理清了“流”与文件操作的基本概念,请不要忘记:
期待在下一篇关于文件读写的博客中与您再次相遇!