【C语言加油站】C语言文件操作详解:从“流”的概念到文件的打开与关闭

文件操作

导读

大家好,很高兴又和大家见面啦!

在之前的文章中,我们已经对"文件"有了比较全面的认识:

  • 文件是存储在计算机上的信息集合,是程序与数据持久化的重要载体

我们了解了文件的基本操作,如打开、读取、写入和关闭,也区分了文本文件与二进制文件的本质不同:

  • 文本文件以字符编码(如ASCII)方式存储,便于人类直接阅读
  • 二进制文件则保留数据的原始格式,更适合机器高效处理

理解了"什么是文件"以及"文件如何存储"之后,我们自然会问:在C语言中,如何具体实现文件的操作

这就涉及到一个核心概念------"流"(Stream),以及与之相关的文件指针和标准流机制。通过"流",C语言将不同的输入输出设备(如键盘、屏幕、磁盘文件)抽象为统一的数据通道,使得文件操作变得灵活而一致。

现在,就让我们一起深入探讨C语言中的"流"与文件操作的具体实现。

一、流与标准流

1.1 流

是一个内涵非常丰富的概念,它既指液体或气体这类物质的移动 ,也是计算机科学中用于抽象描述数据序列或传输过程的重要模型

在物理世界中,"流"最直观的理解就是​​液体或气体的移动​​。例如,河水的流动、空气的流动(风)都是一种流。流体力学就是研究流体运动规律的科学。其核心原理包括:

  • 连续性:流体被视为连续介质,其物理量在空间中连续变化。
  • 能量守恒:以伯努利方程为例,它描述了在不可压缩流体的稳定流动中,速度、压强和高度之间的关系:动能、压力势能和位能之和在流线上保持不变。

在计算机科学中,​​流Stream )​​ 是一个非常重要且基础的抽象概念,它核心是​​数据处理的一种方式​​。它主要用来处理​​一系列有序的数据元素

如何理解这个抽象概念呢?

在成长的过程中,我相信大家都有听过一句话------我们遨游在知识的海洋中

在这句话中,我们就把所学习的各种知识给具象化成了组成海洋的海水,由这些海水汇聚而形成的海洋,我们就将其称之为知识的海洋。

同样的,在计算机邻域中,我们把计算机中的各种数据具象化成河水,这些河水会汇聚成一条河流,不断地在计算机世界中进行流动。

我们可以向这条河流中倒入新的河水(数据),同样的我们也可以从这条河流中取出我们需要的河水(数据)。

但是有一点需要注意:我们不管是倒入新的河水,还是取出我们需要的河水,我们首先需要做的就是找到这条河流,即,我们在进行输入数据和输出数据前,需要先打开流

1.2 标准流

那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?

那是因为C语⾔程序在启动的时候,默认打开了3个流:

  • stdin标准输⼊流:在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。
  • stdout标准输出流:⼤多数的环境中输出⾄显示器界⾯,printf函数就是将信息输出到标准输出流中
  • stderr标准错误流:⼤多数环境中输出到显示器界⾯。

这是默认打开了这三个流,我们使⽤scanfprintf等函数就可以直接进⾏输⼊输出操作的。

stdinstdoutstderr三个流的类型是: FILE* ,通常称为文件指针

C语⾔中,就是通过 FILE* 的文件指针来维护流的各种操作的。

二、文件指针

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

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

例如,VS2013编译环境提供的 stdio.h 头⽂件中有以下的⽂件类型申明:

c 复制代码
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* 的指针变量:

c 复制代码
	FILE* pf = NULL;	// 创建文件指针变量
	// pf------point file:指向文件的指针

这里我们定义了一个指向 FILE 类型数据的指针变量 pf
pf

可以使 pf 指向某个文件的文件信息区(⼀个结构体变量)。
pf 文件信息区

通过该⽂件信息区中的信息就能够访问该⽂件。
pf 文件信息区 文件

也就是说,通过文件指针变量能够间接找到与它关联的⽂件。

三、文件的打开与关闭

在前面的内容中我们就有介绍过,我们可以对文件执行的基本操作有:创建、销毁、读取、写入......

  • 在我们完成了对文件的创建后,如果我们想要对该文件进行内容的读取和写入操作,那我们首先需要做的就是打开该文件;

  • 当我们在使用完文件,甚至想要销毁文件时,我们则需要先关闭该文件

因此我们首先需要学习的基本操作就是文件的打开与关闭。

在C语言的 stdio.h 这个头文件中,有一系列专门用来进行文件操作的库函数:

  • fclose()------用于关闭一个已经打开的流(文件)
  • fflush()------用于强制将输出流或更新(最近操作为输出)缓冲区中的数据立即写入文件
  • fopen()------用于使用指定模式打开一个文件,并关联一个流
  • freopen()------用于将已有的流重新关联到另一个文件
  • setbuf()------用于为指定的流设置自定义缓冲区或禁用缓冲
  • setvbuf()------用于为指定的流设置缓冲模式(全缓冲、行缓冲、无缓冲)和缓冲区

下面我们主要介绍两个函数------fopenfclose

3.1 fopen()

cplusplus.com 网站中有对该函数的详细介绍,有兴趣的朋友可以点击链接进行访问。这里我们对该函数进行一下使用说明:

从上述的函数说明中我们可看到函数的一个大致用法:

  • 向函数中传入两个参数:要操作的文件名以及对文件的操作模式

那么该函数能够对文件进行哪些具体的操作呢?

简单来说,核心操作有3类:

  • 读文件:对指定的存在文件进行读操作,若文件不存在则会返回错误信息
  • 写文件:对指定文件进行写操作,若文件不存在,则会创建一个新文件
  • 添加数据:对指定文件的末尾追加数据,若文件不存在,则会创建一个新文件

在这三类核心操作中,我们可以细分为以下操作:

  • 读文件:对指定的存在文件进行读操作,若文件不存在则会返回错误信息
    • r :只读操作。为了输入数据,打开一个已经存在的文本文件
    • rb:只读操作。为了输入数据,打开一个二进制文件
    • r+:读写操作。为了读和写,打开一个文本文件
    • rb+:读写操作。为了读和写,打开一个二进制文件
  • 写文件:对指定文件进行写操作,若文件不存在,则会创建一个新文件
    • w:只写操作。为了输出数据,打开一个文本文件
    • wb:只写操作。为了输出数据,打开一个二进制文件
    • w+:读写操作。为了读和写,建立一个新的文件
    • wb+:读写操作。为了读和写,建立一个新的二进制文件
  • 添加数据:对指定文件的末尾追加数据,若文件不存在,则会创建一个新文件
    • a:追加操作。向文本文件末尾添加数据
    • ab:追加操作。向二进制文件末尾添加数据
    • a+:读写操作。在文件末尾进行读写
    • ab+:读写操作。在二进制文件末尾进行读写

fopen 这个函数对给定的操作是否成功,会有相应返回值:

  • 文件打开成功:返回一个指向文件的指针
  • 文件打开失败:返回一个空指针。在大多数的库实现中,如果失败了,也会把 errno 变量设置为系统特定的错误代码

3.2 fclose()

该函数的用法比较简单:

  • 向函数中传入一个需要关闭的流

如果关闭成功,函数会返回 0 ,如果关闭失败,函数会返回 EOF。并且即使函数关闭失败,作为参数的 FILE* 指针指向的文件所关联的流也会与该文件和缓冲区断开关联。

3.3 函数的使用

了解了这两个函数的基本用法,下面我们就可以来尝试着使用一下这两个函数了:

c 复制代码
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语言文件操作的核心基础进行了一次深入的探索。让我们一同回顾本次旅程的要点:

  • 核心概念"流":我们理解了"流"(Stream)作为数据通道的强大抽象,它将多样的输入/输出设备统一起来,使得文件操作变得清晰而一致。

  • 默认的"标准流":我们认识了程序启动时自动打开的三个标准流------stdin(标准输入)、stdout(标准输出)和 stderr(标准错误),这正是 scanfprintf 等函数能直接工作的原因。

  • 关键钥匙"文件指针":我们掌握了 FILE* 这个关键数据类型,它是指向文件信息区的指针,是所有文件操作的基础。

  • 文件操作的基本功"打开与关闭":我们重点学习了 fopenfclose 这一对必须成对使用的函数,详细了解了各种文件打开模式(如 r, w, a, r+ 等)的区别与适用场景,并成功进行了实践。

掌握文件的打开与关闭,只是迈入了文件处理世界的第一步。接下来,我们将学习如何具体地对文件进行读取和写入,探索更多强大的文件操作函数。

如果这篇博客帮助您理清了"流"与文件操作的基本概念,请不要忘记:

  • 点赞 👍 - 您的认可是我持续创作的最大动力!

  • 收藏 ⭐ - 方便日后随时查阅,巩固知识。

  • 转发 ➤ - 分享给更多可能正在学习C语言的朋友,大家一起进步!

  • 评论 💬 - 您有任何疑问、想法或指正,都欢迎在评论区留言,我们一起交流探讨!

期待在下一篇关于文件读写的博客中与您再次相遇!

相关推荐
摇滚侠3 小时前
Spring Boot 3零基础教程,依赖管理机制,笔记06
spring boot·笔记·后端
数据库生产实战3 小时前
Oracle LOB使用入门和简单使用,提供学习用的测试用例!
数据库·学习·oracle
武子康3 小时前
Java-144 深入浅出 MongoDB BSON详解:MongoDB核心存储格式与JSON的区别与应用场景
java·开发语言·数据库·mongodb·性能优化·json·bjson
聪明的笨猪猪3 小时前
Java Spring “事务” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
爱喝水的鱼丶3 小时前
SAP-ABAP:SAP中的用户确认对话框:深入理解与实践POPUP_TO_CONFIRM
运维·开发语言·学习·sap·abap
小此方4 小时前
C语言自定义变量类型结构体理论:从初见到精通(上)
c语言·开发语言
努力也学不会java4 小时前
【Java并发】揭秘Lock体系 -- 深入理解ReentrantReadWriteLock
java·开发语言·python·机器学习
lingggggaaaa4 小时前
小迪安全学习笔记(一百零二讲)—— 漏扫项目篇&PoC开发&Yaml语法&插件一键生成&匹配结果&交互提取
笔记·学习·安全·网络安全·交互
里昆4 小时前
【COMSOL】结构力学仿真(压缩弹性体)案例心得
学习