
🏠个人主页:黎雁
🎬作者简介:C/C++/JAVA后端开发学习者
❄️个人专栏:C语言、数据结构(C语言)、EasyX、游戏、规划
✨ 从来绝巘须孤往,万里同尘即玉京

文章目录
- [【C语言文件操作】第一篇:文件基础认知+打开关闭+字符/字符串读写全解析 📂](#【C语言文件操作】第一篇:文件基础认知+打开关闭+字符/字符串读写全解析 📂)
-
- [前景回顾:动态内存核心速记 📝](#前景回顾:动态内存核心速记 📝)
- [一、为什么需要文件?------ 数据持久化的核心需求 🤔](#一、为什么需要文件?—— 数据持久化的核心需求 🤔)
- [二、什么是文件?------ 程序设计中的文件分类 📋](#二、什么是文件?—— 程序设计中的文件分类 📋)
-
- [1. 程序文件](#1. 程序文件)
- [2. 数据文件](#2. 数据文件)
- [3. 补充:文件路径怎么写?](#3. 补充:文件路径怎么写?)
- [三、文本文件 vs 二进制文件 ------ 数据的两种存储形式 🔢](#三、文本文件 vs 二进制文件 —— 数据的两种存储形式 🔢)
-
- [1. 二进制文件](#1. 二进制文件)
- [2. 文本文件](#2. 文本文件)
- [3. 直观示例:数字10的存储对比](#3. 直观示例:数字10的存储对比)
- [四、文件的打开与关闭 ------ 文件操作的"必经步骤" 🔑](#四、文件的打开与关闭 —— 文件操作的“必经步骤” 🔑)
-
- [1. 核心概念:流与标准流](#1. 核心概念:流与标准流)
- [2. 关键角色:文件指针(FILE*)](#2. 关键角色:文件指针(FILE*))
- [3. 打开文件:fopen函数](#3. 打开文件:fopen函数)
- [4. 关闭文件:fclose函数](#4. 关闭文件:fclose函数)
- [5. 常用打开模式汇总(重点记!)](#5. 常用打开模式汇总(重点记!))
- [6. 打开与关闭文件的示例代码](#6. 打开与关闭文件的示例代码)
- [五、文件的顺序读写 ------ 字符与字符串读写 🔤](#五、文件的顺序读写 —— 字符与字符串读写 🔤)
-
- [1. 字符输出:fputc函数](#1. 字符输出:fputc函数)
- [2. 字符输入:fgetc函数](#2. 字符输入:fgetc函数)
- [3. 字符串输出:fputs函数](#3. 字符串输出:fputs函数)
- [4. 字符串输入:fgets函数](#4. 字符串输入:fgets函数)
- [写在最后 📝](#写在最后 📝)
【C语言文件操作】第一篇:文件基础认知+打开关闭+字符/字符串读写全解析 📂
在之前的动态内存管理学习中,我们掌握了堆区内存的手动申请与释放,但动态内存中的数据仅在程序运行期间存在,程序退出后内存被回收,数据也随之丢失。而实际开发中,我们常常需要长期保存数据(比如游戏存档、用户信息),这就离不开文件操作!这一篇我们从文件的核心概念入手,搞定文件的打开与关闭,再掌握字符和字符串的读写方法,打好文件操作的基础!
前景回顾:动态内存核心速记 📝
C 语言动态内存管理入门:malloc/calloc/realloc/free 核心函数详解
C 语言动态内存管理进阶:常见错误排查 + 经典笔试题深度解析
C 语言动态内存管理高阶:柔性数组特性 + 程序内存区域划分全解
回顾动态内存的核心知识点,能帮我们更清晰理解文件操作的必要性:
- 动态内存(堆区)由程序员手动管理,生命周期跟随程序运行,程序退出后内存回收,数据丢失。
- 栈区(局部变量)、数据段(全局/静态变量)的内存也无法实现数据的长期持久化存储。
- 文件存储在磁盘等外部设备中,不受程序运行状态影响,是实现数据持久化的核心方案。
一、为什么需要文件?------ 数据持久化的核心需求 🤔
我们先看一个简单场景:写一个计算程序,每次运行输入两个数字,计算结果后退出。如果没有文件,下次再运行程序,之前的计算结果根本无法追溯;但如果把每次的计算结果写入文件,即使程序退出,数据也会保存在磁盘上,后续随时可以打开文件查看。
核心结论:文件的核心作用是实现数据持久化,将程序运行时的临时数据存储到外部设备(磁盘),让数据脱离程序生命周期长期存在。
二、什么是文件?------ 程序设计中的文件分类 📋
我们平时说的"文件"是磁盘上的文件(比如文档、图片),但在C语言程序设计中,文件按功能分为两类,本讲重点关注数据文件:
1. 程序文件
用于支撑程序编译、运行的文件,核心是"服务于程序本身",比如:
- 源程序文件(.c后缀,我们编写的代码文件)
- 目标文件(Windows下.obj后缀,编译后生成)
- 可执行程序(Windows下.exe后缀,链接后生成)
2. 数据文件
本讲核心重点!文件内容是程序运行时需要读写的数据,用于"存储程序处理的信息",比如游戏存档文件、用户信息文件、日志文件等。
数据文件的交互逻辑:test.c(源程序) → 编译生成 test.exe(可执行程序) → 程序运行时,从数据文件读取 数据(输入),或向数据文件写入数据(输出)。
3. 补充:文件路径怎么写?
要操作文件,必须告诉程序文件的位置,这就需要指定文件路径。文件路径由"路径+文件名主干+后缀"组成,分为两种常用类型:
- 绝对路径 :从磁盘根目录开始的完整路径,比如
"D:\\Code\\test.txt"(注意:Windows下路径分隔符"\"需要转义,写成"\",避免被解析为特殊字符)。 - 相对路径 :相对于程序运行目录的路径,更简洁常用:
"test.txt"或"./test.txt":当前程序所在目录的test.txt文件"../test.txt":程序所在目录的上一级目录的test.txt文件"../../test.txt":程序所在目录的上两级目录的test.txt文件
三、文本文件 vs 二进制文件 ------ 数据的两种存储形式 🔢
按数据的组织形式,数据文件分为文本文件和二进制文件,核心区别是"数据存储时是否进行ASCII码转换":
1. 二进制文件
数据在内存中以二进制形式存储,写入文件时不进行任何转换,直接输出二进制数据。优点是存储高效、节省空间,缺点是用普通文本编辑器打开会显示乱码,需要专用工具查看。
2. 文本文件
数据存储时需要先转换为ASCII码形式,再写入文件。优点是用普通文本编辑器就能直接查看,缺点是转换过程会消耗资源,且占用更多存储空间。
3. 直观示例:数字10的存储对比
以数字10为例,看看两种文件的存储差异:
- 内存中二进制存储:无论哪种文件,数据在内存中都是二进制
00001010(4字节int类型); - 文本文件存储:转换为字符'1'和'0'的ASCII码,即
00110001 00110000,占用2字节; - 二进制文件存储:直接将内存中的二进制数据写入,即
00001010(实际为4字节,高位补0),占用4字节。
四、文件的打开与关闭 ------ 文件操作的"必经步骤" 🔑
操作文件的核心流程是"打开文件 → 读写操作 → 关闭文件",就像我们打开笔记本写字、写完后合上一样,缺一不可。
1. 核心概念:流与标准流
程序需要和不同外部设备(键盘、显示器、文件、网络)交互,而不同设备的读写规则不同。为了统一操作,C语言抽象出流的概念,把所有设备的交互都封装成"流",程序只需操作流即可,无需关注具体设备。
C语言程序启动时,会默认打开3个标准流(均为FILE*类型的文件指针):
stdin:标准输入流,默认从键盘输入(scanf函数就是从这里读取数据);stdout:标准输出流,默认输出到显示器(printf函数就是向这里输出数据);stderr:标准错误流,默认输出到显示器(用于输出程序错误信息)。
2. 关键角色:文件指针(FILE*)
在缓冲文件系统中,每个被使用的文件,系统都会在内存中开辟一块"文件信息区",存储文件的路径、状态、当前读写位置等信息。这些信息被封装在一个FILE类型的结构体中(不同编译器的FILE声明略有差异,但使用方式一致)。
我们通过定义FILE*类型的指针(文件指针)来指向这个文件信息区,后续所有对文件的操作,都通过这个指针完成。比如:FILE* pf; ,pf 就是一个文件指针,指向某个文件的信息区。
3. 打开文件:fopen函数
函数原型:FILE* fopen(const char* filename, const char* mode);
filename:要打开的文件名(可带路径);mode:文件打开模式(决定了文件的操作类型,比如只读、只写、追加等);- 返回值:打开成功,返回指向文件信息区的指针(非NULL);打开失败,返回NULL指针(必须检查!)。
4. 关闭文件:fclose函数
函数原型:int fclose(FILE* stream);
stream:要关闭的文件指针;- 核心作用:释放文件信息区的内存,关闭流,避免资源泄漏;
- 注意:关闭文件后,要将文件指针置为NULL,避免成为野指针。
5. 常用打开模式汇总(重点记!)
| 打开模式 | 含义 | 若文件不存在的行为 |
|---|---|---|
"r"(只读) |
打开已存在的文本文件,用于读取数据 | 出错(返回NULL) |
"w"(只写) |
打开文本文件,用于写入数据(覆盖原有内容) | 创建新文件 |
"a"(追加) |
向文本文件末尾添加数据(不覆盖原有内容) | 创建新文件 |
"rb"(只读) |
打开已存在的二进制文件,用于读取数据 | 出错(返回NULL) |
"wb"(只写) |
打开二进制文件,用于写入数据(覆盖原有内容) | 创建新文件 |
"ab"(追加) |
向二进制文件末尾添加数据(不覆盖原有内容) | 创建新文件 |
6. 打开与关闭文件的示例代码
c
#include <stdio.h>
int main()
{
// 打开test.txt文件,以只读模式(文本文件)
FILE* pf = fopen("test.txt", "r");
// 必须检查文件是否打开成功
if (pf == NULL)
{
perror("fopen"); // 打印错误信息(比如"fopen: No such file or directory")
return 1; // 打开失败,退出程序
}
// 这里写后续的读写操作...
printf("打开文件成功\n");
// 关闭文件
fclose(pf);
pf = NULL; // 置空指针,避免野指针
return 0;
}
五、文件的顺序读写 ------ 字符与字符串读写 🔤
顺序读写是指从文件开头到末尾依次读写数据,是最基础的文件读写方式。C语言提供了专门的函数用于字符和字符串的顺序读写,这些函数不仅适用于文件流,还适用于标准流(比如从键盘读、向屏幕写)。
1. 字符输出:fputc函数
函数原型:int fputc(int character, FILE* stream);
character:要写入的字符(实际传入的是ASCII码值,char类型会自动提升为int);stream:文件指针(写入文件传文件指针,写入屏幕传stdout);- 返回值:写入成功返回写入的字符ASCII码;写入失败返回EOF(值为-1)。
示例:向文件写入字符'a'到'z'
c
#include <stdio.h>
int main()
{
// 打开data.txt文件,以只写模式(文本文件)
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
// 逐个写入字符'a'、'b'、'c'、'd'
fputc('a', pf);
fputc('b', pf);
fputc('c', pf);
fputc('d', pf);
// 循环写入'a'到'z'
for (int i = 'a'; i <= 'z'; i++)
{
fputc(i, pf);
}
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
效果:data.txt文件中会写入"abcd...xyz";若将pf改为stdout,则字符会直接输出到屏幕。
2. 字符输入:fgetc函数
函数原型:int fgetc(FILE* stream);
stream:文件指针(读文件传文件指针,读键盘传stdin);- 返回值:读取成功返回读取的字符ASCII码;读取失败或遇到文件末尾,返回EOF(-1)。
示例:从文件中逐个读取字符并输出到屏幕
c
#include <stdio.h>
int main()
{
// 打开data.txt文件,以只读模式(文本文件)
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
// 读取第一个字符
int ch = fgetc(pf);
printf("%c\n", ch); // 输出:a
// 读取第二个字符
ch = fgetc(pf);
printf("%c\n", ch); // 输出:b
// 循环读取剩余所有字符,直到文件末尾
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3. 字符串输出:fputs函数
函数原型:int fputs(const char* str, FILE* stream);
str:要写入的字符串(以'\0'结尾);stream:文件指针(写入文件/屏幕);- 注意:fputs不会自动添加换行符,需要换行需手动加'\n'。
示例:向文件写入字符串
c
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
// 写入字符串,手动加换行符
fputs("hello world\n", pf);
fputs("hello bit", pf);
fclose(pf);
pf = NULL;
return 0;
}
效果:data.txt文件中内容为"hello world"(换行)和"hello bit"。
4. 字符串输入:fgets函数
函数原型:char* fgets(char* str, int num, FILE* stream);
str:存储读取字符串的数组;num:最多读取的字符数(包含'\0',实际最多读取num-1个字符);stream:文件指针;- 停止条件:① 读取到num-1个字符;② 遇到换行符;③ 遇到文件末尾(满足任一即停止);
- 返回值:读取成功返回str的地址;读取失败或遇到文件末尾返回NULL。
示例:从文件中读取字符串
c
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[20]; // 存储读取的字符串
fgets(arr, 20, pf); // 最多读取19个字符(+1个'\0')
printf("读取的字符串:%s", arr); // 输出:hello world(带换行)
fclose(pf);
pf = NULL;
return 0;
}
写在最后 📝
这一篇我们掌握了文件操作的基础核心:理解了文件的作用和分类,搞定了文件打开与关闭的关键步骤(尤其是打开模式和空指针检查),还学会了字符和字符串的读写函数。这些是文件操作的基石,后续的格式化读写、二进制读写都需要基于这些知识。
核心要点总结
- 文件操作流程:打开(fopen)→ 读写 → 关闭(fclose),缺一不可,且必须检查fopen返回值。
- fputc/fgetc用于字符读写,fputs/fgets用于字符串读写,支持文件流和标准流。
- 路径分为绝对路径和相对路径,Windows下绝对路径的"\"需要转义为"\"。
下一篇我们将学习更实用的格式化读写(比如读写结构体数据)和二进制读写,进一步提升文件操作能力!