C语言学习笔记:
各位于晏,亦菲们,请点赞关注!
我的个人主页:
目录
1、文件
1.1、文件的概念
1.1.1、物理层面
文件是存储在 存储介质(如硬盘、固态硬盘、U 盘等)上的数据集合。从物理角度看,存储介质被划分为一个个小的存储单元,文件的数据就存储在这些单元中。这些存储单元通过文件系统进行管理,文件系统会记录每个文件在存储介质中的位置、大小等信息。例如,在硬盘中,文件的数据是以二进制的形式存储在磁盘的磁道和扇区上,就像图书馆里的书籍被放置在不同的书架和格子里一样。
1.1.2、逻辑层面
一般来说,文件按照其组织形式被分为文本文件和二进制文件:
文本文件:
文本文件是一种常见的文件类型,它以字符编码(如 ASCII、UTF - 8 等)的形式存储数据。文本文件中的内容可以被人类直接阅读和理解,例如,一个包含诗歌的文本文件,我们可以使用文本编辑器打开它,看到其中的文字内容。文本文件中的每一行都以换行符(在不同操作系统中有不同的表示,如在 Linux 中是 “\n”,在 Windows 中是 “\r\n”)结束。
二进制文件:
它存储的数据不是以字符形式直接可读的,而是以二进制的形式存储各种类型的数据,如整数、浮点数、图像数据、音频数据等。例如,一个可执行程序文件就是二进制文件,它包含了机器指令,这些指令只有计算机能够理解和执行。对于图像文件,它存储了图像的像素信息、颜色模式等数据,这些数据以特定的二进制格式排列,我们需要使用专门的图像软件才能正确地解析和显示图像
文件的组织结构:
文件通常包含两部分,即文件头和文件数据。文件头包含了关于文件的一些元信息,如文件类型、文件大小、创建日期等。文件数据则是文件的主要内容,根据文件类型的不同,文件数据可以是文本内容、图像数据、音频数据等各种类型的数据。以常见的 JPEG 图像文件为例,文件头包含了图像的分辨率、色彩模式等信息,文件数据则是经过压缩的图像像素数据。
1.2、文件的类型
按文件用途分类:
可以将文件分为程序文件和数据文件
程序文件:
源代码文件:
这是程序员编写程序的基本文件,如前面提到的 C 语言的 .c 文件、Java 的 .java 文件,Windows下的.exe可执行文件以及.ojb二进制文件。这些文件包含了用高级编程语言编写的代码,通过编译器等工具可以将其转换为可执行文件或其他中间文件形式。例如,一个简单的 C 语言程序文件可能包含了变量定义、函数声明和调用等代码内容,它是程序开发的基础文件。
配置文件:用于存储程序的配置信息,如 .ini 文件、.conf 文件等。这些文件可以定义程序的各种参数,如数据库连接字符串、服务器端口号、界面布局参数等。以一个 Web 服务器软件为例,它的配置文件可能会包含监听的 IP 地址和端口、默认文档目录等信息,通过修改配置文件可以改变程序的运行方式。
资源文件:程序运行过程中可能需要使用的各种资源,如图片、音频、字体等文件都可以看作是资源文件。在游戏开发中,游戏中的角色形象文件、背景音乐文件等都是资源文件。这些文件可以在程序运行时被加载和使用,以丰富程序的功能和用户体验。
数据文件 :
数据库文件:
用于存储大量的结构化数据,如 MySQL 的 .myd、.myi 文件(分别存储数据和索引信息)、SQLite 的 .db 文件等。数据库文件通过数据库管理系统进行管理,用户可以通过 SQL(结构化查询语言)等方式对其中的数据进行增删改查操作。例如,一个企业的客户关系管理系统(CRM)的数据库文件中存储了客户的基本信息、交易记录等大量数据,这些数据可以通过数据库软件进行高效的管理和查询。
数据存储文件(CSV、JSON 等):CSV(逗号分隔值)文件:这是一种简单的文本格式文件,用于存储表格数据。它以纯文本形式存储数据,每行代表一条记录,每列之间用逗号(或其他指定的分隔符)隔开。例如,一个存储学生成绩的 CSV 文件可能如下:姓名,语文成绩,数学成绩,英语成绩;张三,80,90,70;李四,75,85,80,这种格式的文件可以很方便地被电子表格软件(如 Excel)读取和处理,也常用于数据交换和简单的数据存储。
JSON(JavaScript 对象表示法)文件:它是一种轻量级的数据交换格式,以文本形式存储数据,但具有一定的结构性。JSON 文件可以存储对象、数组等复杂的数据结构,例如 {"students":[ {"name": "张三", "scores": {"语文": 80, "数学": 90, "英语": 70}}, {"name": "李四", "scores": {"语文": 75, "数学": 85, "英语": 80}} ]},这种格式在 Web 开发、移动应用开发等领域用于数据传输和存储,因为它可以很容易地在不同的编程语言之间进行解析和生成。
本章我们主要讨论的是数据文件
1.3、文件名
文件名是计算机文件系统中用于标识和区分不同文件的名称
1.3.1、命名规则
字符限制:
不同的操作系统对文件名的字符长度有不同的限制
例如,在 Windows 操作系统中,文件名(包括路径)的最大长度通常为 260 个字符左右。而在 Linux 等操作系统中,文件名长度限制相对较为宽松,往往可以达到数千个字符。
文件名中允许使用的字符也因操作系统而异。在 Windows 中,文件名可以包含字母(A - Z、a - z)、数字(0 - 9)、一些特殊字符如空格、下划线(_)、连字符(-)、点号(.)等,但不能使用以下字符:\ / : *? " < > | ,因为这些字符在 Windows 的文件系统中有特殊用途(如 \ 用于路径分隔等)。在 Linux 中,几乎可以使用所有的 ASCII 字符,但斜杠(/)是目录分隔符,不能在文件名中使用,并且点号(.)开头的文件名在某些情况下有特殊含义(如隐藏文件)。
大小写敏感性:
Windows 文件系统通常不区分文件名的大小写,例如,“test.txt” 和 “TEST.TXT” 被视为同一个文件。而 Linux 等系统则区分文件名大小写,“test.txt” 和 “TEST.TXT” 是两个不同的文件
1.3.2、组成部分
文件名一般包含基本名和扩展名两个部分,中间用"."隔开
基本名用于标识文件的主要内容或用途。例如,在 “document.docx” 中,“document” 就是基本名,它可以让用户大致了解文件可能是一个文档类型的文件。
扩展名则通常表示文件的类型或格式。在上述例子中,“.docx” 是扩展名,它表明该文件是一个 Microsoft Word 文档格式的文件。不同的扩展名对应不同的文件类型,如 “.txt” 表示文本文件,“.jpg” 表示 JPEG 图像文件,“.mp3” 表示音频文件等。通过扩展名,操作系统和应用程序可以识别文件的格式,从而采用相应的方式打开、处理或执行该文件。
常见的文件扩展名及文件类型:
文件类型 | 扩展名 | 说明 |
---|---|---|
文本文件 | .txt | 纯文本格式,内容仅为简单文字,无复杂格式 |
办公文档 | .docx | 由 Microsoft Word 创建的现代办公文档格式,基于 XML,功能丰富 |
.xlsx | Excel 电子表格文件,用于数据处理与分析等 | |
.pptx | PowerPoint 演示文稿文件,用于制作幻灯片展示内容 | |
便携式文档 | 可跨平台保持格式一致呈现文档内容,便于阅读与分享,格式固定 | |
压缩文件 | .zip | 通用的压缩格式,能将多个文件或文件夹压缩整合,便于存储和传输 |
.rar | 常用压缩格式,具有较高压缩率等特点 | |
图像文件 | .jpg(.jpeg) | 采用有损压缩的图像格式,能较好平衡图像质量和文件大小,常用于照片存储 |
.png | 支持无损压缩和透明背景的图像格式,常用于网页元素、图标等 | |
音频文件 | .mp3 | 广泛使用的音频压缩格式,音质较好且文件较小,适合音乐播放 |
.wav | 未经压缩的波形音频格式,音质保真度高,但文件较大 | |
视频文件 | .mp4 | 常见的视频格式,具有良好的兼容性与压缩效果,广泛应用于网络视频等领域 |
.avi | 一种较老但仍常用的视频格式,支持多种编码方式 | |
可执行文件 | .exe | Windows 操作系统下的可执行程序文件,双击即可运行相应软件或功能 |
网页文件 | .html(.htm) | 用于构建网页的基础文本格式文件,包含网页的文本、结构、链接及多媒体等元素呈现的代码 |
编程文件 | .c | C 语言的源程序代码文件,用于编写 C 语言相关程序 |
.cpp | C++ 语言的源程序代码文件,基于 C 语言扩展,支持面向对象编程等特性 | |
.java | Java 语言的源程序代码文件,编写 Java 相关应用程序,跨平台特性强 | |
.py | Python 语言的源程序代码文件,语法简洁,应用广泛 |
1.3.3、作用
文件标识与区分:
文件名是文件在文件系统中的唯一标识(在同一目录下),它使得用户和计算机能够准确地找到和区分不同的文件。例如,在一个包含众多文件的文件夹中,如果没有文件名,就无法确定哪个文件是自己需要的。
与应用程序关联:
文件名的扩展名与应用程序相关联,当用户双击一个文件时,操作系统会根据文件的扩展名来确定使用哪个应用程序来打开该文件。例如,当双击 “.pdf” 文件时,操作系统会启动已安装的 PDF 阅读器应用程序来打开它;双击 “.mp4” 文件时,则会调用视频播放器来播放视频内容。
1.3.4、文件名和路径
路径:
路径则是描述文件在文件系统中的位置。路径可以分为绝对路径和相对路径
绝对路径:
是从文件系统的根目录开始,完整地描述文件位置的路径。例如,在 Windows 系统中,绝对路径可能像 “C:\Users\Username\Documents\example.txt”,这里 “C:\” 是根目录,“Users\Username\Documents” 是目录层次结构,最后是文件名 “example.txt”。在 Linux 系统中,绝对路径以 “/” 开头,如 “/home/user/Documents/example.txt”。
相对路径:
是相对于当前工作目录来描述文件位置的路径。假设当前工作目录是 “C:\Users\Username\Documents”,那么文件 “example.txt” 的相对路径可能就是 “example.txt”(如果在同一目录下),或者如果文件在子目录 “Subfolder” 中,相对路径可能是 “Subfolder\example.txt”
完整文件标识:
当我们要准确地指定一个文件以便计算机能够找到并操作它时,就需要同时使用路径和文件名。它们共同构成了文件的完整标识,让操作系统知道去哪里找到这个特定的文件。
2、文件的打开和关闭
2.1、流和标准流
2.1.1、流
在计算机编程中,流是一种抽象的概念,表示数据的流动。它可以被看作是一个数据序列,数据从一个源(如文件、网络连接、内存中的数据结构等)流向一个目的地(如显示器、文件、另一个网络连接等)。就像是水流通过管道一样,数据在流中按照一定的顺序传输。
字节流和字符流:
字节流以字节(8 位二进制位)为单位处理数据,适用于处理二进制数据(如图像、音频、视频文件等)。字符流则以字符为单位处理数据,主要用于处理文本数据,它会根据字符编码(如 ASCII、UTF - 8 等)来处理字符,更关注字符的语义。
输入流和输出流:
输入流用于从源读取数据到程序中,例如从文件中读取内容、从网络接收数据等。输出流用于将程序中的数据发送到目的地,如将数据写入文件、向网络发送数据等。在 C 语言中,stdin(标准输入)是一个输入流,用于读取用户输入的数据;stdout(标准输出)和stderr(标准错误输出)是输出流,用于输出信息和错误信息。
2.1.2、标准流
标准流是操作系统为每个程序预先定义好的特殊流。
一般来说,标准流包括标准输入流(stdin),标准输出流(stdout),标准错误输出流(stderr)它们提供了程序与外部世界(如用户、其他程序等)进行输入输出通信的标准方式。这些流在程序启动时就已经自动打开,程序可以直接使用它们而不需要进行额外的打开操作。
标准输入stdin:
这是程序接收输入数据的主要途径,通常默认关联到键盘。在命令行环境下,当程序等待用户输入时,就是通过stdin读取用户在键盘上输入的字符序列。例如,在 C 语言中,可以使用scanf函数从stdin读取用户输入的数据
除了键盘,stdin也可以通过重定向的方式从其他数据源获取数据。例如,在 Linux 系统的命令行中,可以使用<操作符将一个文件的内容重定向为程序的标准输入,像./my_program < input.txt这样的命令会使my_program程序将input.txt文件的内容当作用户输入来读取。
标准输出stdout:
主要用于程序向外部输出正常的信息,默认关联到显示器屏幕。在 C 语言中,printf函数用于向stdout输出格式化后的字符串。例如,一个简单的程序输出计算结果或者一些提示信息给用户,就是通过stdout实现的。
和stdin一样,stdout也可以被重定向。在Linux中,可以使用>操作符将stdout的输出重定向到一个文件中,如./my_program > output.txt会将程序my_program的标准输出内容保存到output.txt文件中,而不是显示在屏幕上。
标准错误输出stderr:
用于输出错误信息和诊断信息。它和stdout的区别在于,即使stdout被重定向,stderr通常仍然会将信息输出到屏幕上,以便用户能够及时看到程序出现的错误。例如,在 C 语言中,当程序出现运行时错误(如文件打开失败等),可以使用fprintf(stderr, "Error message")来输出错误信息。在 Linux系统中,2>操作符可以单独重定向stderr,如./my_program 2> error.txt会将程序的错误信息保存到error.txt文件中。
标准流对比:
标准流 | 描述 | 默认关联设备 | 常用函数示例 | 用途说明 |
---|---|---|---|---|
stdin | 标准输入流,用于接收程序的输入数据 | 键盘 | scanf 、fgets (从 stdin 读取时) | 程序运行时获取用户输入的数据,例如在命令行程序中让用户输入数值、字符串等信息,以便程序根据输入进行相应处理。 |
stdout | 标准输出流,用于输出程序的正常运行结果和信息 | 显示器屏幕 | printf 、puts | 将程序计算结果、提示信息、格式化数据等输出显示给用户,如输出程序执行过程中的阶段性结果、最终答案等,让用户了解程序的运行情况和获取所需信息。 |
stderr | 标准错误输出流,专门用于输出错误信息和诊断信息 | 显示器屏幕 | fprintf(stderr, "错误信息") | 当程序出现错误(如文件打开失败、内存分配错误、逻辑错误等)时,将错误详细信息输出到屏幕,以便开发者快速定位和排查问题,即使 stdout 被重定向,stderr 通常仍能直接在屏幕显示,确保错误信息不被忽略。 |
我们在写C语言程序时,并没有主动打开标准流,那为什么我们可以使用printf向stdout输出,使用scanf往stdin读取呢?
程序启动时由操作系统打开:
在 C 语言程序启动时,操作系统会自动为程序打开标准流。这是操作系统提供的一种基本服务,目的是让程序能够方便地与外部环境进行交互。对于大多数操作系统而言,当一个 C 语言程序开始运行时,它会为程序建立与标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)对应的流对象。这些流对象与特定的设备或文件关联,一般情况下,stdin关联到键盘设备用于接收用户输入,stdout关联到显示器屏幕用于输出正常的程序结果和信息,stderr也关联到显示器屏幕但主要用于输出错误信息。
从操作系统的角度看,这是一种统一的、标准化的方式来管理程序的输入输出。这样,无论是简单的命令行程序还是复杂的图形界面程序(通过终端输出信息的部分),都可以使用相同的机制来进行基本的信息交互,而不需要程序自己去初始化和管理与这些基本输入输出设备的连接。
语言运行时环境的支持
C 语言的运行时环境(如 C 标准库)也在其中起到了辅助作用。运行时环境知道如何与操作系统提供的这些已打开的标准流进行交互,并提供了一系列函数(如printf用于stdout、scanf用于stdin、fprintf用于输出到指定流等)来方便程序员在程序中使用这些标准流进行输入输出操作。运行时环境会维护这些标准流的状态信息,例如,它会跟踪stdin的读取位置、stdout和stderr的输出缓冲区状态等。这些状态信息对于正确地实现输入输出功能(如处理换行符、缓冲数据的刷新等)是非常重要的。
2.2、文件指针
在 C 语言中,文件指针是一种特殊的指针,它用于指向一个和文件相关的结构体对象(FILE结构体)。这个FILE结构体包含了文件操作所需的各种信息,如文件缓冲区的位置、文件的当前读写位置、文件的打开模式等。文件指针实际上是对文件进行操作的一个接口,通过它可以访问和控制文件的各种属性和数据。
VS2013中<stdio.h>提供的FILE结构体对象:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
文件指针的声明和初始化
声明:
文件指针的声明方式与普通指针类似,其类型为FILE *,例如:
//声明文件指针
FILE* fp;
初始化:
文件指针通常通过fopen函数进行初始化。fopen函数用于打开一个文件,并返回一个指向该文件对应的FILE结构体的指针。例如:
int main()
{
FILE* fp = fopen("test.txt", "w");
if (fp == NULL)
{
perror("fopen");
exit(1);
}
return 0;
}
2.3、fopen和fclose
fopen和fclose是C标准库提供用于打开和关闭文件的函数,C标准规定,fopen用来打开文件,fclose用来关闭文件。
2.3.1、fopen
函数原型:
FILE * fopen ( const char * filename, const char * mode );
参数:
filename:
这是一个字符串,用于指定要打开的文件的名称。这个名称可以包含文件的路径,路径可以是绝对路径或者相对路径(相对于当前工作目录的路径)。如果文件名不包含路径,程序会在当前工作目录下查找文件。
mode:
这也是一个字符串,用于指定文件的打开方式
返回值:
如果文件打开成功,fopen函数返回一个指向FILE结构体的非NULL指针。这个指针可以用于后续的文件操作,如读取、写入和定位文件中的数据。
如果文件打开失败(文件不存在且打开模式为读模式,或者没有足够的权限打开文件等原因),函数返回NULL。在实际编程中,应该始终检查fopen函数的返回值,以避免对无效的文件指针进行操作导致程序出错。例如:
int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen");
return 1;
}
return 0;
}
常见的fopen打打开方式:
打开方式 | 描述 | 示例 |
---|---|---|
"r" | 以只读方式打开一个已存在的文件。如果文件不存在,返回NULL 。文件指针定位在文件开头,用于读取文件内容。 | FILE *fp = fopen("test_file.txt", "r"); |
"w" | 以写入方式打开文件。如果文件不存在,创建新文件;如果文件已存在,清空文件内容后准备写入。 | FILE *fp = fopen("test_file.txt", "w"); |
"a" | 以追加方式打开文件。如果文件不存在,创建新文件;如果文件已存在,将文件指针移到文件末尾,用于在文件末尾添加内容。 | FILE *fp = fopen("test_file.txt", "a"); |
"r +" | 以读写方式打开一个已存在的文件。文件指针定位在文件开头,既可以读取文件内容,也可以写入内容覆盖原有数据。 | FILE *fp = fopen("test_file.txt", "r +"); |
"w +" | 以读写方式打开文件。如果文件不存在,创建新文件;如果文件已存在,清空文件内容后可以进行读写操作。 | FILE *fp = fopen("test_file.txt", "w +"); |
"a +" | 以读写方式打开一个已存在的文件。文件指针初始位置在文件末尾,主要用于追加数据和读取文件内容。 | FILE *fp = fopen("test_file.txt", "a +"); |
"rb" | 以二进制只读方式打开一个已存在的文件。如果文件不存在,返回NULL 。用于读取二进制文件内容。 | FILE *fp = fopen("test_file.txt", "rb"); |
"wb" | 以二进制写入方式打开文件。如果文件不存在,创建新文件;如果文件已存在,清空文件内容后准备写入二进制数据。 | FILE *fp = fopen("test_file.txt", "wb"); |
"ab" | 以二进制追加方式打开文件。如果文件不存在,创建新文件;如果文件已存在,将文件指针移到文件末尾,用于在二进制文件末尾添加内容。 | FILE *fp = fopen("test_file.txt", "ab"); |
"r + b" 或"rb +" | 以二进制读写方式打开一个已存在的文件。文件指针定位在文件开头,既可以读取也可以写入二进制数据覆盖原有数据。 | FILE *fp = fopen("test_file.txt", "rb +"); |
"w + b" 或"wb +" | 以二进制读写方式打开文件。如果文件不存在,创建新公司;如果文件已存在,清空文件内容后可以进行二进制数据的读写操作。 | FILE *fp = fopen("test_file.txt", "wb +"); |
"a + b" 或"ab +" | 以二进制读写方式打开一个已存在的文件。文件指针初始位置在文件末尾,主要用于追加二进制数据和读取二进制文件内容。 | FILE *fp = fopen("test_file.txt", "ab +"); |
底层原理:(了解)
当调用fopen函数时,程序会向操作系统请求打开指定的文件。操作系统首先会根据文件名和路径在文件系统中查找文件。如果是相对路径,会结合当前工作目录来定位文件。
找到文件后,操作系统会检查程序对文件的访问权限是否符合打开模式的要求。例如,对于只读模式打开的文件,会检查程序是否有读取权限;对于写模式或读写模式打开的文件,会检查是否有写入权限等。
如果权限检查通过,操作系统会为该文件分配一些必要的资源,如文件缓冲区(用于缓存文件数据,提高读写效率),并创建一个FILE结构体对象来记录文件的相关信息(如文件状态、缓冲区位置、当前读写位置等),然后返回一个指向这个FILE结构体的指针给程序
2.3.2、fclose
函数原型:
int fclose ( FILE * stream );
参数:
stream:这是一个指向FILE结构体的指针,这个指针通常是通过fopen函数成功打开文件后获得的。它用于指定要关闭的文件。
返回值:
如果文件成功关闭,fclose函数返回0。
如果出现错误(如文件指针无效、文件已经被关闭或者其他与文件关闭相关的错误),函数返回EOF(在<stdio.h>中定义,通常是-1)。例如:
int main(){
FILE* fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
// 文件操作...
if (fclose(fp)== EOF) {
perror("文件关闭失败");
// 可能需要进行错误处理
}
return 0;
}
文件关闭的重要性:
资源释放:
当文件打开时,操作系统会为其分配各种资源,如内存缓冲区用于缓存文件数据、文件描述符占用系统的资源空间等。关闭文件可以释放这些资源,避免资源浪费。特别是在处理大量文件或者长时间运行的程序中,未关闭的文件可能会累积,最终耗尽系统资源。
数据完整性保证:在文件写入操作后,如果不关闭文件,可能会导致数据没有完全写入存储设备。这是因为操作系统可能会将数据先缓存在内存中,等待合适的时机(如缓冲区满或者文件关闭时)再将数据真正写入磁盘等存储介质。所以及时关闭文件可以确保文件数据完整地保存到存储设备中。
3、文件的顺序读写
3.1、字符读写函数
字符读写函数有fgetc和fputc
3.1.1、fgetc
函数原型:
int fgetc ( FILE * stream );
参数:
stream是一个指向FILE结构体的文件指针,表示要从中读取字符的文件流
功能描述:
从指定的文件流stream中读取一个字符。它会将文件指针向前移动一个字符的位置。例如,从一个文本文件中逐个字符地读取内容。
返回值:
成功读取一个字符时,返回读取到的字符(以unsigned char类型转换为int类型返回);如果遇到文件末尾,返回EOF(在<stdio.h>中定义,通常为-1);如果读取过程中出现错误,也返回EOF。
示例:
int main() {
FILE* fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
int c;
while ((c = fgetc(fp)) != EOF) {
putchar(c);
}
fclose(fp);
return 0;
}
这段代码打开一个名为test.txt的文件用于读取。在while循环中,每次调用fgetc函数从文件中读取一个字符,并将其存储在变量c中。如果c不等于EOF,就表示还没有到达文件末尾,通过putchar函数将读取到的字符输出到标准输出(屏幕)。当读取到EOF时,循环结束,最后关闭文件
3.1.2、fputc
函数原型:
int fputc ( int character, FILE * stream );
参数:
character:要写入文件的字符,这个字符以int类型传入,但实际上只有低字节部分(对于 ASCII 字符,其 ASCII 码值在 0 - 255 之间)会被写入文件。例如,要写入字符'A',可以传入(int)'A'或者65(因为'A'的 ASCII 码值为 65)。
stream:和fgetc函数中的stream类似,是一个指向FILE结构体的文件指针,表示要写入字符的目标文件流。
功能描述:
将一个字符c写入指定的文件流stream中。写入成功后,文件指针会向前移动一个字符的位置。例如,将一个字符逐个写入一个文本文件。
返回值:
如果写入成功,返回写入的字符(c的值);如果出现错误,返回EOF。
int main(){
FILE* fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
int c = 'A';
fputc(c, fp);
fclose(fp);
return 0;
}
这段代码打开一个名为test.txt的文件用于写入。将字符'A'赋值给变量c,然后通过fputc函数将c写入文件。最后关闭文件,此时文件test.txt中会有一个字符'A'。
3.2、字符串读写函数
字符串读写函数包括fgets和fputs
3.2.1、fgets
函数原型:
char * fgets ( char * str, int num, FILE * stream );
参数:
str:一个字符数组的首地址,用于存储从文件中读取的字符串。这个数组的大小应该足够大,以避免缓冲区溢出。例如,可以定义char str[100];,然后将str作为s参数传递给fgets函数,用于存储读取的字符串。
num:表示最多读取n - 1个字符。这是为了在读取的字符串末尾留出一个字节的空间来存储字符串结束标志\0。例如,如果n = 10,那么最多读取 9 个字符,然后加上\0构成一个合法的 C 字符串。
stream:指向要读取字符串的文件流的指针,和前面的函数类似。
功能描述:
从文件流stream中读取最多n - 1个字符,并将它们存储到字符数组s中。读取过程中遇到换行符\n或者文件末尾时停止读取。读取的字符串会在末尾自动添加\0作为字符串结束标志
返回值:
如果成功读取,返回字符数组s的首地址;如果遇到文件末尾且没有读取到任何字符,返回NULL;如果读取过程中出现错误,返回NULL。
int main() {
FILE* fp = fopen("test.txt", "r");
char str[100];
if (fp == NULL) {
perror("perror fopen");
return 1;
}
fgets(str, sizeof(str)-1, fp);
printf("%s", str);
fclose(fp);
return 0;
}
打开test.txt文件用于读取。定义一个字符数组str,大小为 100。使用fgets函数从文件流fp指向的文件中读取最多sizeof(str)-1(即 99)个字符,并将其存储在str数组中。读取的字符串会在末尾自动添加\0作为结束标志。最后将读取的字符串输出到屏幕上,然后关闭文件
3.2.2、fputs
函数原型:
int fputs ( const char * str, FILE * stream );
参数:
str:一个常量字符串的首地址,这个字符串是要写入文件的内容。可以是一个字面量字符串"Hello, World!",也可以是一个字符数组的首地址(前提是数组中存储的是以\0结尾的字符串)。
stream:目标文件流的指针,用于指定将字符串写入哪个文件。
返回值:
如果成功,则返回一个非负值。如果出现错误,该函数返回EOF并设置错误指示器(error)。
int main() {
FILE* fp = fopen("test.txt", "w");
char str[] = "Hello, World!";
if (fp == NULL) {
perror("fopen");
return 1;
}
fputs(str, fp);
fclose(fp);
return 0;
}
打开test.txt文件用于写入,通过fputs函数将字符串str写入文件,最后关闭文件
3.3、格式化读写函数
格式化读写函数包括fprintf和fscanf
3.3.1、fprintf
fprintf是格式化输出函数
函数原型:
int fprintf ( FILE * stream, const char * format, ... );
参数:
stream:指向文件流的指针,用于指定将数据写入哪个文件。
format:格式控制字符串,用于指定写入数据的格式。这个字符串和printf函数的格式控制字符串类似,包含各种格式说明符和转义字符等。例如,format可以是"整数:%d,浮点数:%f",用于将整数和浮点数按照指定格式写入文件。
...:可变参数列表,包含要写入文件的数据。这些数据的类型和顺序应该与format字符串中的格式说明符相对应。例如,如果format中有%d和%f,那么...部分应该依次有一个整数和一个浮点数。
返回值:
如果成功,则返回写入的字符总数。如果出现写错误,则设置错误指示器(error)并返回一个负数。如果在写宽字符时出现多字节字符编码错误,则errno被设置为EILSEQ并返回一个负数。
int main(){
FILE* fp = fopen("test.txt", "w");
int num = 10;
if (fp == NULL) {
perror("fopen");
return 1;
}
fprintf(fp, "输入的整数为:%d", num);
fclose(fp);
return 0;
}
以"w"的方式打开test.txt文件用于写入,通过fprintf函数按照"输入的整数为:%d"的格式将变量num的值写入文件,最后关闭文件。最后"test.txt"中的内容为:输入的整数为:10
3.3.2、fscanf
fscanf是格式化输入函数
函数原型:
int fscanf ( FILE * stream, const char * format, ... );
参数:
stream:指向文件流的指针,指定从哪个文件中读取数据。
format:格式控制字符串,用于指定读取数据的格式。这个字符串的格式和scanf函数的格式控制字符串类似,包含各种格式说明符(如%d用于读取整数,%f用于读取浮点数,%s用于读取字符串等)和可选的修饰符。例如,format可以是"%d %f %s",表示从文件中依次读取一个整数、一个浮点数和一个字符串。
...:可变参数列表,表示根据format字符串中的格式说明符,要接收读取数据的变量地址。例如,如果format中有%d,%s,那么在...部分应该有一个int类型变量的地址,和char*变量的地址用于存储读取的整数。
返回值:
返回成功读取的数据项的个数;如果在读取任何数据之前遇到文件末尾,返回
EOF
;如果读取过程中出现错误,返回一个不确定的值。
int main() {
FILE* fp = fopen("test.txt", "r");
int num;
char str[20];
if (fp == NULL) {
perror("fopen");
return 1;
}
fscanf(fp, "%s,%d",str,&num);
printf("读取到的字符串为:%s,整数为:%d\n",str,num);
fclose(fp);
return 0;
}
这段代码以"r"的方式打开文件"test.txt"并使用fscanf从中读取一个字符串和整数,并且以printf的方式打印在显示器上,最后关闭文件
3.4、二进制读写函数
3.4.1、fread
函数原型:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
参数:
void *ptr:是一个指向内存缓冲区的指针,用于存储从文件中读取的数据。这个缓冲区的大小应该足够容纳要读取的数据量
size_t size:表示每个数据元素的大小(以字节为单位)。
size_t count:表示要读取的数据元素的数量。
FILE *stream:指向要读取的文件的指针。
返回实际读取的数据元素的数量。
如果读取过程中遇到文件末尾或者发生错误,这个返回值可能小于count。可以通过检查返回值来判断是否成功读取了期望的数据量。
3.4.2、fwrite
函数原型:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
参数:
const void *ptr:这是一个指向要写入文件的数据块的指针。这个数据块可以是任何类型的数据,如数组、结构体等。
size_t size:表示每个数据元素的大小(以字节为单位)。
size_t count:表示要写入的数据元素的数量。
FILE *stream:这是指向目标文件的指针,通过fopen函数打开文件后得到。
返回值:
如果写入操作成功,返回实际写入的数据元素的数量,这个数量可能小于count(例如,当磁盘空间不足等情况时)。如果返回值与count不相等,可能表示出现了部分写入或者写入错误。
示例代码:
创建结构体:
struct Person {
char name[20];
int age;
};
定义文件指针和结构体数组:
FILE* fp;
struct Person people[3] = { {"Alice", 25}, {"Bob", 30}, {"Charlie", 35} }; // 要写入文件的结构体数组
struct Person read_people[3]; // 用于存储从文件中读取数据的结构体数组
以二进制写入模式打开文件:
// 以二进制写入模式打开文件
fp = fopen("people.bin", "wb");
if (fp == NULL) {
perror("Error opening file for writing");
return 1;
}
使用fwrite将结构体数组写入文件:
size_t written_count = fwrite(people, sizeof(struct Person), 3, fp);
if (written_count == 3) {
printf("结构体数据成功写入文件\n");
}
else {
perror("Error writing data to file");
}
fclose(fp); // 关闭写入文件的文件指针
以二进制读取模式打开文件:
fp = fopen("people.bin", "rb");
if (fp == NULL) {
perror("Error opening file for reading");
return 1;
}
使用fread从文件中读取数据到结构体数组:
size_t read_count = fread(read_people, sizeof(struct Person), 3, fp);
if (read_count == 3) {
printf("成功从文件中读取结构体数据:\n");
for (int i = 0; i < 3; i++) {
printf("姓名: %s, 年龄: %d\n", read_people[i].name, read_people[i].age);
}
}
else if (read_count > 0) {
printf("部分读取,读取了 %zu 个结构体数据:\n", read_count);
for (int i = 0; i < read_count; i++) {
printf("姓名: %s, 年龄: %d\n", read_people[i].name, read_people[i].age);
}
}
else {
perror("Error reading data from file");
}
fclose(fp); // 关闭读取文件的文件指针
程序输出:
3.4.3、应用场景和优势
数据存储与备份:
使用fwrite可以将程序中的数据结构(如学生信息结构体数组)写入文件进行存储备份。
数据恢复与加载:fread函数用于从之前保存的数据文件中读取数据,恢复程序所需的数据。例如,当程序再次启动时,可以使用fread从文件中读取之前保存的信息,继续进行相关操作。
文件复制:结合fread和fwrite可以实现文件的复制功能。先使用fread从源文件读取数据块,然后使用fwrite将读取的数据块写入目标文件。
二进制数据处理优势:
对于二进制数据的处理,这两个函数尤为重要。与处理文本文件的函数不同,fwrite和fread在读写过程中不会对数据进行格式化转换,能够原封不动地读写二进制数据,如结构体、二进制图像数据、音频数据等复杂的二进制格式。
例如,在处理一个简单的位图(BMP)图像文件时,图像文件的数据是按照二进制格式存储的。可以使用fread按照 BMP 文件的格式规范(如文件头、像素数据等部分的字节大小和顺序)从文件中读取数据到内存中的结构体数组,以方便后续对图像数据进行处理。
常见读写函数对比:
函数名 | 函数原型 | 参数 | 返回值 | 功能概述 |
---|---|---|---|---|
fgetc | int fgetc(FILE *stream) | FILE *stream :文件指针,指向要读取字符的文件 | 成功时返回读取到的字符(转换为int 类型),读到文件末尾或出错时返回EOF (通常为-1 ) | 从指定文件中读取一个字符 |
fputc | int fputc(int character, FILE *stream) | int character :要写入的字符FILE *stream :文件指针,指向要写入字符的文件 | 成功时返回写入的字符,出错时返回EOF | 将一个字符写入指定文件 |
fgets | char *fgets(char *str, int num, FILE *stream) | char *str :存储读取字符串的字符数组int num :最多读取的字符数(包括'\0' )FILE *stream :文件指针,指向要读取字符串的文件 | 成功时返回s (即字符数组首地址),读到文件末尾时返回s ,出错时返回NULL | 从指定文件中读取一行字符串(最多n - 1 个字符),并在末尾添加'\0' |
fputs | int fputs(const char *str, FILE *stream) | const char *str :要写入文件的字符串FILE *stream :文件指针,指向要写入字符串的文件 | 成功时返回非负整数,出错时返回EOF | 将字符串写入指定文件,不写入'\0' |
fprintf | int fprintf(FILE *stream, const char *format,...) | FILE *stream :文件指针,指向要写入的文件const char *format :格式化字符串... :可变参数列表,根据格式化字符串的要求提供相应数据 | 成功时返回写入的字符数,出错时返回负数 | 按照指定格式将数据写入文件 |
fscanf | int fscanf(FILE *stream, const char *format,...) | FILE *stream :文件指针,指向要读取的文件const char *format :格式化字符串... :可变参数列表,用于存储读取的数据 | 成功时返回成功读取并赋值的变量个数,读到文件末尾时返回EOF ,出错时返回EOF 或负数 | 按照指定格式从文件中读取数据并赋值给相应变量 |
fread | size_t fread(void *ptr, size_t size, size_t count, FILE *stream) | void *ptr :指向存储读取数据的内存缓冲区size_t size :每个数据元素的大小size_t count :要读取的数据元素数量FILE *stream :文件指针,指向要读取的文件 | 成功时返回实际读取的数据元素数量,可能小于count (如文件末尾或出错) | 从文件中读取数据块到内存缓冲区 |
fwrite | size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream) | const void *ptr :指向要写入文件的数据块size_t size :每个数据元素的大小size_t count :要写入的数据元素数量FILE *stream :文件指针,指向要写入的文件 | 成功时返回实际写入的数据元素数量,可能小于count (如磁盘空间不足等) | 将内存中的数据块写入文件 |
4、字符串格式化函数
4.1、sscanf
sscanf是字符串格式化输入函数
函数原型:
int sscanf ( const char * s, const char * format, ...);
参数:
s:这是一个常量字符串,是要从中读取数据的源字符串。
format:这是格式控制字符串,用于指定如何从str中读取数据。格式控制字符串的规则和scanf函数类似,包含各种格式说明符(如%d用于读取整数,%f用于读取浮点数,%s用于读取字符串等)和可选的修饰符。例如,"%d %f %s"这样的格式控制字符串表示要从str中依次读取一个整数、一个浮点数和一个字符串。
...:这是可变参数列表,表示根据format字符串中的格式说明符,要接收读取数据的变量地址。例如,如果format中有%d,那么在...部分应该有一个int类型变量的地址,用于存储读取的整数。假设定义了int num; float fnum; char str[20];,那么...部分可以是&num, &fnum, str。
返回值:
如果读取成功,该函数返回参数列表中成功填充的项数。在匹配失败的情况下,该计数可以匹配预期的项目数,也可以更少(甚至为零)。如果在成功解释任何数据之前出现输入失败,则返回EOF。
int main(){
char origin_str[] = "2024 3.1415926 Hello";
int year; float p;char str[10];
int result = sscanf(origin_str, "%d %f %s", &year, &p, str);
if (result == 3) {
printf("成功读取三个数据:year=%d,Π近似=%f,str=%s\n", year, p, str);
}
else if (result == EOF) {
printf("在读取任何数据之前遇到字符串末尾\n");
}
else {
printf("读取过程中出现错误\n");
}
return 0;
}
首先定义了一个包含数字和字符的字符串origin_str,以及用于存储读取数据的变量year,p,以及str,使用sscanf函数从origin_str中按照"%d %f %s"的格式读取数据。sscanf函数返回读取的数据项个数存储在result变量中。
根据result的值进行判断:如果result等于3,说明成功读取了三个数据项,并将这些数据输出;如果result等于EOF,表示在读取任何数据之前就遇到了字符串末尾;如果result是其他值,说明读取过程中出现了错误
4.2、sprintf
sscanf是字符串格式化输出函数
函数原型:
int sprintf ( char * str, const char * format, ... );
参数:
str:这是一个字符数组,用于存储格式化后的字符串。这个数组的大小应该足够大,以避免缓冲区溢出。
format:这是格式控制字符串,用于指定数据的输出格式。格式控制字符串的规则和printf函数类似,包含各种格式说明符和转义字符等。
...:这是可变参数列表,包含要写入字符串的数据。这些数据的类型和顺序应该与format中的格式说明符相对应。例如,如果format中有%d、%f和%s,那么...部分应该依次有一个整数、一个浮点数和一个字符串。
返回值:
返回成功写入str字符串的字符数(不包括字符串结束符\0),如果失败,则返回一个负数。
int main()
{
int num = 10;
float f = 3.14159;
char str[] = "HelloWorld";
char obj_str[100];
int result = sprintf(obj_str, "整数:%d,浮点数:%f,字符串:%s", num, f, str);
if (result >= 0) {
printf("成功写入字符串:%s\n", obj_str);
}
else {
printf("写入过程中出现错误\n");
}
return 0;
}
首先定义了一个整数num、一个浮点数fnum、一个字符串str和一个用于存储格式化后字符串的字符数组obj_str。
然后使用sprintf函数将num、fnum和str按照"整数:%d,浮点数:%f,字符串:%s"的格式组合成一个新的字符串,并存储到obj_str中。sprintf函数返回写入output_str的字符数存储在result变量中。
根据result的值进行判断:如果result大于等于0,说明成功写入了字符串,将output_str的内容输出;如果result小于0,说明写入过程中出现了错误。
对比一组函数 :
函数名 | 功能 | 函数原型 | 参数 | 返回值 |
---|---|---|---|---|
scanf | 从标准输入读取格式化数据 | int scanf(const char *format,...) | format :格式控制字符串,规定读取数据的格式;... :可变参数,对应接收读取数据的变量地址 | 成功读取的数据项个数;若读取前遇标准输入末尾返回EOF ;读取出错返回不确定值 |
fscanf | 从文件流读取格式化数据 | int fscanf(FILE *stream, const char *format,...) | stream :指向文件的文件流指针;format :格式控制字符串;... :可变参数,用于存储读取的数据 | 成功读取的数据项个数;遇文件流末尾返回EOF ;读取出错返回不确定值 |
sscanf | 从字符串读取格式化数据 | int sscanf(const char *s, const char *format,...) | str :要从中读取数据的源字符串;format :格式控制字符串;... :可变参数,用于接收读取的数据 | 成功读取的数据项个数;遇字符串末尾返回EOF ;读取出错返回不确定值 |
printf | 向标准输出写入格式化数据 | int printf(const char *format,...) | format :格式控制字符串,指定输出数据的格式;... :可变参数,包含要输出的数据 | 成功输出的字符数(不含字符串结束符\0 ) |
fprintf | 向文件流写入格式化数据 | int fprintf(FILE *stream, const char *format,...) | stream :指向文件的文件流指针;format :格式控制字符串;... :可变参数,包含要输出的数据 | 成功写入文件的字符数(不含\0 );出现错误返回负数 |
sprintf | 向字符串写入格式化数据 | int sprintf(char *str, const char *format,...) | str :用于存储格式化后数据的目标字符串;format :格式控制字符串;... :可变参数,包含要输出的数据 | 成功写入str 的字符数(不含\0 );若str 缓冲区不够大导致截断,返回负数 |
5、文件的随机读写
文件的随机读写涉及三个函数,分别是fseek,ftell和rewind
5.1、fseek
函数原型:
int fseek ( FILE * stream, long int offset, int origin );
参数:
FILE *stream:这是一个指向被操作文件的指针。这个指针是通过fopen函数打开文件后返回的。它代表了要进行随机访问操作的文件对象。
long int offset:偏移量,用于指定相对于origin位置的字节数。如果offset是正数,文件指针向文件末尾方向移动;如果是负数,文件指针向文件开头方向移动。
int origin:用于指定偏移量offset的参考位置。它有三个宏定义的值:SEEK_SET:文件开头,偏移量从文件的第一个字节开始计算。
SEEK_CUR:当前位置,偏移量从文件指针当前位置开始计算。
SEEK_END:文件末尾,偏移量从文件的最后一个字节开始计算。
origin:
返回值:
如果操作成功,返回 0;如果操作失败,返回一个非零值。常见的错误包括试图移动文件指针到一个无效的位置,如超出文件范围等情况。如果发生读写错误,则设置错误指示器
功能概述:
用于改变文件指针的位置,实现文件的随机定位,使后续的文件读写操作能够从指定的新位置开始进行,打破顺序读写的局限,可灵活地在文件内任意位置跳转
5.2、ftell
函数原型:
long int ftell ( FILE * stream );
参数:
FILE *stream:同样是指向目标文件的指针,和fseek函数中一样,用于指定要获取文件指针位置的那个文件
返回值:
调用成功情况下,返回文件指针当前位置相对于文件开头的字节数。但如果出现错误,例如文件指针处于一个无效状态或者文件操作出现异常等情况,返回值为-1L。
功能概述:
获取文件指针当前所处位置相对于文件开头的字节数,便于知晓当前在文件中的读写进度或者为后续基于当前位置的其他操作提供参考依据。
5.3、rewind
函数原型:
void rewind ( FILE * stream );
参数:
FILE *stream:指向需要将文件指针重置到开头的文件的指针
返回值:
该函数无返回值,它的作用主要就是对文件指针位置进行重置这一操作。
功能概述:
将所指定文件的文件指针直接移动回文件的开头位置,方便重新开始对文件进行顺序读取或者重新执行某些从开头开始的文件操作流程,相当于执行了fseek(fp, 0, SEEK_SET);但效率可能更高且代码书写更简洁直观。
示例代码:
int main() {
FILE* fp = fopen("test.txt", "r");
if (fp != NULL) {
fseek(fp, 5, SEEK_SET);
long pos = ftell(fp);
printf("fseek后位置: %ld\n", pos);
rewind(fp);
pos = ftell(fp);
printf("rewind后位置: %ld\n", pos);
fclose(fp);
}
return 0;
}
这段代码先以"r"的方式打开文件"test.txt",如果打开成功,用fseek将指针移到第 5 字节处,用ftell查看位置并打印,再用rewind重置指针,最后再次用ftell查看并打印位置后关闭文件。
6、文件读取结束的判定
先认识一个函数,feof
6.1、feof
函数原型:
int feof ( FILE * stream );
参数:
FILE* stream:指向目标文件的文件指针,一般是用fopen打开的文件指针
返回值:
检测到文件指针已经到达文件末尾时,返回一个非零值(通常是 1),这个非零值表示文件结束的状态。当文件指针还没有到达文件末尾时,feof函数返回 0。
功能概述:
feof函数主要用于检查文件读取是否完整。当对一个文件进行循环读取操作时,例如使用fgetc、fgets或者fread等函数读取文件内容,在循环结束后(通常是遇到文件结束标志EOF),可以使用feof函数来确定文件是正常读取结束,还是因为读取错误而中断。
示例:
int main(){
FILE* fp = fopen("data.bin", "rb");
int num;
if (fp == NULL) {
perror("fopen");
return 1;
}
while (fread(&num, sizeof(int), 1, fp) == 1)
{
printf("%d", num);
}
if (feof(fp)) {
printf("二进制文件正常读取完毕\n");
} else{
printf("二进制文件读取出错\n");
}
fclose(fp);
return 0;
}
牢记:
feof不能用来判断文件是否读取结束,feof只能判断文件读取的原因是否是正常读取到文件末尾结束,或者读取出错导致的结束。
那我们在使用文件读取函数时如何判断文件是否读取结束呢?通过函数的返回值来判断
6.2、文本文件
判断文本文件是否读取结束通过判断fgetc的返回值是否为EOF,fgets的返回值是否为NULL,并且结合feof判断文件结束的状态
fgetc:
int main(){
int c;
FILE* fp = fopen("test1.txt", "r");
if (!fp) {
perror("fopen");
return 1;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) { // 标准C I/O读取文件循环
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp)) //表示未读取到末尾,文件读取中从出错
printf("I/O error when reading");
else if (feof(fp)) //表示文件指针到末尾,文件正常读取结束
printf("End of file reached successfully");
fclose(fp);
}
fgets:
int main(){
FILE* fp = fopen("test.txt", "r");
char buffer[100];
if (fp == NULL){
perror("fopen");
return 1;
}
//文件正常读取结束或者读取遇到错误都会返回NULL
while (fgets(buffer, sizeof(buffer), fp) != NULL){
printf("%s", buffer);
}
//判断是什么原因结束的
if (ferror(fp)) //表示未读取到末尾,文件读取中从出错
printf("I/O error when reading");
else if (feof(fp)) //表示文件指针到末尾,文件正常读取结束
printf("End of file reached successfully");
fclose(fp);
return 0;
}
6.3、二进制文件
判断二进制文件是否读取结束,可以通过判断fread的返回值是否小于实际要读的个数,同样结合feof判断文件结束的状态
int main(){
FILE* fp = fopen("test.bin", "rb");
int arr[100];
size_t ret = fread(arr, sizeof(int), 100, fp); //
if (ret== 100) {
printf("Array read successfully, contents: ");
//处理读到的数据
}
else { // error handling
if (feof(fp)) //文件遇到意外的结尾
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) { //文件读取出错
perror("Error reading test.bin");
}
}
fclose(fp);
}
7、文件读写错误的判断
ferror
函数原型:
int ferror ( FILE * stream );
参数:
FILE* stream:要判断的目标文件指针
返回值:
它用于检查在对指定文件流stream进行的读写操作中是否发生了错误。
当文件流stream在最近一次操作中出现错误时,ferror函数返回一个非零值(通常是 1),表示有错误发生;如果没有错误,ferror函数返回 0。
int main() {
FILE* pFile = fopen("myfile.txt", "w");
if (pFile == NULL) perror("fopne failed");
else {
fputc('x', pFile);
if (ferror(pFile)) //如果返回非0,代表文件写入出错
printf("Error Writing to myfile.txt\n");
fclose(pFile);
}
return 0;
}
8、文件缓冲区
文件缓冲区是一块内存区域,用于临时存储文件读写的数据。在计算机系统中,文件的读写操作相对较慢,尤其是涉及到磁盘等外部存储设备。使用缓冲区可以减少与外部设备的交互次数,从而提高文件读写的效率。
例如,当程序向文件写入数据时,数据首先被放入缓冲区。当缓冲区满了或者满足某些特定条件(如程序显式地刷新缓冲区、文件关闭等)时,缓冲区中的数据才会被一次性写入文件。对于读取文件操作,也是类似的原理,从文件中读取一批数据放入缓冲区,程序从缓冲区中获取数据,而不是每次都直接从文件读取,这样可以减少磁盘 I/O 操作的次数。
8.1、全缓冲
在全缓冲模式下,只有当缓冲区被填满时,数据才会被真正地写入文件或者从文件中读取新的数据。当向一个全缓冲的文件流写入数据时,操作系统会将数据暂时存储在内存缓冲区中。只有当缓冲区达到其最大容量时,数据才会被真正地写入磁盘文件。这样做的主要目的是为了减少磁盘 I/O 操作的次数,因为磁盘 I/O 相对内存操作来说速度较慢。对于标准输出(例如stdout),如果它被重定向到文件,通常是全缓冲的。
int main(){
FILE* pf = fopen("test.txt", "w");
//数据先被存放在缓冲区,此时文件内是没有内容的
fputs("abcdef", pf);
//休眠10秒,此时写入操作已经完成
Sleep(10000);
//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)此时文件内就有了数据
fflush(pf);
fclose(pf);
//注:fclose在关闭文件的时候,也会刷新缓冲区
return 0;
}
8.2、无缓冲
无缓冲模式下,数据会立即被写入文件或者从文件读取,没有缓冲区的延迟。stderr(标准错误输出)通常是无缓冲的,这是为了确保错误信息能够及时显示,不会因为缓冲区的问题而延迟。
int main()
{
fprintf(stderr, "无缓冲,stderr会立即显示错误\n");
// 数据会立即输出,不会缓冲
return 0;
}
由于stderr是无缓冲的,信息会立即显示在控制台上,而不需要等待缓冲区的填充或其他触发条件。
C语言知识总结
C语言文件操作知识涵盖多方面内容,这里我们只是对常见的文件操作的函数进行讲解和使用,文件操作从 fopen 开启文件,失败则报错。成功后可顺序读写,如字符、字符串、格式化、二进制读写,也可随机读写,利用 fseek、ftell、rewind。读写时靠 feof 判断结束,ferror 查错并处理。最后 fclose 关闭,异常则有提示,形成完整操作链路,以此构建基本的文件操作知识框架,为后续在C语言探索的道路上乘风破浪打下坚实的基础!
如上的讲解只是我的一些拙见,如有不足之处,还望各位大佬不吝在评论区予以斧正,感激不尽!创作不易,还请多多互三支持!你们的支持是我最大的动力!
标签:fp,文件,高效,读取,映射,int,写入,内存,FILE From: https://blog.csdn.net/bite_zwy/article/details/144451888