【C语言】文件操作

个人主页Yue丶越
个人专栏C语言Python基础


前言

在C语言编程中,我们常常会遇到这样的问题:程序运行时处理的数据,一旦因程序退出就会随着内存回收而丢失。我们想要让数据长期保存、重复使用,则必须掌握其文件的操作。所以本文将基于C语言文件操作的核心知识点,从基础概念到实战技巧,带你全面解锁于文件操作的奥秘。


一、为什么需要文件操作?

程序在运行时,所有数据都存储在内存中。内存的特性是"断电易失",比如我们编写一个计算程序,计算结果如果只存在变量里,程序关闭后就再也找不回了。

而文件存储在磁盘(硬盘)等外部存储设备中,数据则可长期保留。通过文件操作,我们能实现:

  • 程序运行数据的持久化保存(如用户信息、配置参数)。
  • 跨程序数据共享(如A程序写入的数据,B程序可读取)。
  • 大规模数据处理(如读取超大日志文件、写入海量计算结果)。

简单来说,文件就是程序与外部存储设备之间的数据桥梁。


二、文件的核心概念

在C语言中,文件主要分为两类,且每个文件都有唯一标识。

2.1 文件的分类(按功能)

  • 程序文件 :用于存放程序代码和编译结果,比如.c源文件、.obj目标文件、.exe可执行文件。
  • 数据文件 :用于存储程序运行时读写的数据(本文重点讨论),比如存储用户信息的user.txt、存储二进制数据的data.bin

2.2 文件名的组成

一个完整的文件名包含3部分,三者缺一不可:

文件路径 + 文件名主干 + 文件后缀

示例:c:\code\test.txt

  • 路径:c:\code\(指定文件所在位置)。
  • 主干:test(文件的核心标识)。
  • 后缀:.txt(标识文件类型,文本文件)。

2.3 文本文件 vs 二进制文件(按数据组织形式)

数据在内存中始终以二进制形式存储,但写入文件时,可选择两种存储方式:

存储类型 存储形式 特点 示例(存储整数10000)
文本文件 ASCII码形式 人类可读,占用空间较大 占用5个字节('1'/'0'/'0'/'0'/'0')
二进制文件 原生二进制形式 人类不可读,占用空间小 占用4个字节(VS2019环境)

代码验证二进制存储

c 复制代码
#include <stdio.h>
int main() {
    int a = 10000;
    // 以二进制只写模式打开文件
    FILE* pf = fopen("test.bin", "wb");
    if (pf != NULL) {
        // 写入4字节二进制数据
        fwrite(&a, 4, 1, pf);
        fclose(pf);
        pf = NULL;
    }
    return 0;
}

运行后用二进制编辑器打开test.bin,可看到存储内容为10 27 00 00(十六进制),对应10000的二进制值。


三、文件操作的核心

C语言通过"流"和"文件指针"简化对不同设备的操作。

1. 流(Stream)

不同外部设备(键盘、显示器、磁盘)的输入输出逻辑不同,C语言抽象出"流"的概念(可理解为"流淌着数据的河")。

程序所有的输入输出操作,本质都是对"流"的操作:

  • 读数据:从流中"捞取"数据到程序。
  • 写数据:将程序数据"注入"流中,再由流传递到设备。

2. 标准流(默认打开)

C程序启动时,会自动打开3个标准流,无需手动操作:

  • stdin:标准输入流(默认对应键盘),scanf就是从这里读数据。
  • stdout:标准输出流(默认对应显示器),printf就是向这里写数据。
  • stderr:标准错误流(默认对应显示器),用于输出程序错误信息。

这3个流的类型都是FILE*,即文件指针。

3. 文件指针(FILE*)

操作文件时,系统会在内存中创建一个"文件信息区"(结构体FILE实例),存储文件名、状态、当前读写位置等信息。

我们通过FILE*类型的指针来操作这个信息区,进而间接操作文件:

c 复制代码
// 定义文件指针
FILE* pf;
// 打开文件后,pf指向该文件的信息区
pf = fopen("test.txt", "r");

可以理解为:文件指针是程序与文件之间的"遥控器"。


四、文件的操作(打开、读写、关闭)

文件操作的基本流程是:打开文件 → 读写操作 → 关闭文件,任何一步都不能马虎。

4.1 打开文件:fopen函数

c 复制代码
FILE *fopen(const char *filename, const char *mode);
  • filename:文件路径+文件名(如"test.txt""c:\code\data.bin")。
  • mode:打开模式(核心参数,决定读写权限和文件类型)。
  • 返回值:成功则返回文件指针,失败则返回NULL(必须检查!)。
常用文件打开模式
模式 含义 文件不存在时 适用场景
"r" 文本文件只读 报错 读取已存在的文本文件
"w" 文本文件只写 创建新文件 新建或覆盖文本文件
"a" 文本文件追加 创建新文件 向文本文件末尾添加数据
"rb" 二进制文件只读 报错 读取已存在的二进制文件
"wb" 二进制文件只写 创建新文件 新建或覆盖二进制文件
"r+" 文本文件读写 报错 读写已存在的文本文件
"w+" 文本文件读写 创建新文件 新建可读写的文本文件
打开文件的正确形式(必须检查返回值)
c 复制代码
FILE* pf = fopen("test.txt", "w");
// 检查文件是否打开成功
if (pf == NULL) {
    // 打印错误信息
    perror("fopen failed");
    // 退出程序
    return 1;
}

4.2 关闭文件:fclose函数

c 复制代码
int fclose(FILE *stream);
  • 功能:关闭文件,释放文件信息区和相关资源。
  • 注意:关闭后必须将文件指针置为NULL,避免野指针。
  • 示例:
c 复制代码
// 关闭文件
fclose(pf);
// 置空指针
pf = NULL;

4.3 顺序读写(按"从头到尾"的顺序操作)

顺序读写是最常用的文件操作方式,C语言提供了8个核心函数,覆盖字符、行、格式化、二进制四种场景:

函数名 功能 适用场景 示例
fgetc 读取单个字符 所有输入流 char c = fgetc(pf);
fputc 写入单个字符 所有输出流 fputc('A', pf);
fgets 读取一行文本 所有输入流 char buf[1024]; fgets(buf, 1024, pf);
fputs 写入一行文本 所有输出流 fputs("hello", pf);
fscanf 格式化读取 所有输入流 fscanf(pf, "%d %s", &num, str);
fprintf 格式化写入 所有输出流 fprintf(pf, "name:%s age:%d", name, age);
fread 二进制读取 文件 fread(buf, sizeof(int), 5, pf);
fwrite 二进制写入 文件 fwrite(arr, sizeof(int), 5, pf);
关键区别:fscanf/printf与scanf/printf
  • scanf:仅从标准输入流(键盘)读取。
  • fscanf:可从任意输入流(文件/键盘)读取。
  • printf:仅向标准输出流(显示器)写入。
  • fprintf:可向任意输出流(文件/显示器)写入。
示例:用fprintf/fscanf读写文本文件
c 复制代码
#include <stdio.h>

int main() {
    FILE* pf = fopen("user.txt", "w+");
    if (pf == NULL) {
        perror("fopen failed");
        return 1;
    }

    // 写入数据
    fprintf(pf, "name:zhangsan age:20\n");
    fprintf(pf, "name:lisi age:22\n");

    // 回到文件起始位置(否则读不到数据)
    rewind(pf);

    // 读取数据
    char name[20];
    int age;
    while (fscanf(pf, "name:%s age:%d", name, &age) != EOF) {
        printf("read: name=%s, age=%d\n", name, age);
    }

    fclose(pf);
    pf = NULL;
    return 0;
}

4.4 随机读写:随心所欲定位读写位置

有时我们需要跳过部分内容直接读写(比如修改文件中间的某段数据),这就需要随机读写函数:

(1)fseek:定位文件指针
c 复制代码
int fseek(FILE *stream, long int offset, int origin);
  • offset:偏移量(正数向后移,负数向前移)。
  • origin:起始位置(3个可选值)。
    • SEEK_SET:文件起始位置(常用)。
    • SEEK_CUR:文件当前位置。
    • SEEK_END:文件末尾位置。

示例:修改文件中间内容

c 复制代码
FILE* pf = fopen("example.txt", "wb");
fputs("This is an apple.", pf);
// 从文件开头偏移9个字节(指向"apple"的'a')
fseek(pf, 9, SEEK_SET);
// 覆盖写入,结果为"This is a sam."
fputs(" sam", pf);
fclose(pf);
(2)ftell:获取指针偏移量
c 复制代码
long int ftell(FILE *stream);

功能:返回文件指针相对于起始位置的偏移量(可用于获取文件大小):

c 复制代码
// 定位到文件末尾
fseek(pf, 0, SEEK_END);
// 获取文件大小(字节数)
long size = ftell(pf);
(3)rewind:重置指针到起始位置
c 复制代码
void rewind(FILE *stream);

功能:快速将文件指针回到文件开头,等价于fseek(pf, 0, SEEK_SET)


五、文件读取结束的判定

很多初学者会误用feof函数判断文件是否结束,但这是一个高频坑点!

正确认知feof

feof的作用不是"判断文件是否结束",而是"判断文件结束的原因是否是遇到文件尾"。

正确的判定方式

  1. 文本文件:判断读写函数的返回值:

    • fgetc:返回EOF(-1)表示结束。
    • fgets:返回NULL表示结束。
    • fscanf:返回值小于格式说明符个数表示结束。
  2. 二进制文件 :判断fread的返回值是否小于要读取的元素个数:

c 复制代码
// 二进制文件读取示例
size_t ret = fread(buf, sizeof(double), 5, pf);
if (ret < 5) {
    // 读取结束或出错
    if (feof(pf)) {
        printf("文件读取完毕\n");
    } else if (ferror(pf)) {
        printf("读取错误\n");
    }
}

错误示例

c 复制代码
// 错误:feof在读取到文件尾后才返回真,会多循环一次
while (!feof(pf)) {
    fgetc(pf);
}

六、底层原理:文件缓冲区

C语言采用"缓冲文件系统",系统会为每个打开的文件分配一块内存缓冲区,这会影响数据写入的时机:

  • 写数据:先写入缓冲区,缓冲区满了才会批量写入磁盘。
  • 读数据:先从磁盘读取数据填满缓冲区,再从缓冲区供程序读取。

刷新缓冲区的3种方式

  1. 调用fflush(pf)(注意:高版本VS已不支持)。
  2. 关闭文件(fclose会自动刷新缓冲区)。
  3. 缓冲区满(系统自动刷新)。

示例:缓冲区的影响

c 复制代码
FILE* pf = fopen("test.txt", "w");
// 数据先写入缓冲区,文件中暂时没有内容
fputs("abcdef", pf);
// 睡眠10秒,此时打开文件看不到内容
Sleep(10000);
// 刷新缓冲区,数据写入磁盘
fflush(pf);
// 睡眠10秒,此时打开文件能看到内容
Sleep(10000);
fclose(pf);

关键结论

操作文件后,必须关闭文件手动刷新缓冲区,否则数据可能留在缓冲区,导致文件内容不完整!


文件操作是C语言的核心技能之一,无论是日常开发还是面试,都是高频考点。建议结合本文的示例代码,亲手编写几个实战案例(比如读写用户信息、存储数组数据、修改文件内容),则能彻底掌握!

至此,我们已梳理完"文件操作"的全部内容了。最后我们在文末来进行一个投个票,告诉我你对哪部分内容最感兴趣、收获最大,也欢迎在评论区聊聊你的学习感受,如果在实践中遇到具体问题,也欢迎在评论区留言讨论,祝大家编程顺利!


以上就是本期博客的全部内容了,感谢各位的阅读以及关注。如有内容存在疏漏或不足之处,恳请各位技术大佬不吝赐教、多多指正,咱们下篇内容见。

以上。

祝你早安午安晚安。

盈盈赴暖,事事舒然。

相关推荐
哈市雪花2 小时前
记录一次cmake无法正确使用vcpkg的问题
开发语言·c++
笙枫2 小时前
Agent 进阶设计:状态管理、中间件与多Agent协作
java·服务器·python·ai·中间件
Trouvaille ~2 小时前
【C++篇】C++11新特性详解(三):高级特性与实用工具
开发语言·c++·stl·lambda·完美转发·包装器·可变参数模版
有趣灵魂2 小时前
Java-根据HTTP链接读取文件转换为base64
java·开发语言·http
YJlio2 小时前
Disk2vhd 学习笔记(13.1):在线 VHD 冷备份与迁移实战
服务器·笔记·学习
AC赳赳老秦2 小时前
CSV大文件处理全流程:数据清洗、去重与格式标准化深度实践
大数据·开发语言·人工智能·python·算法·机器学习·deepseek
2501_930707782 小时前
如何使用C#代码将 Excel 文件转换为 SVG
开发语言·c#·excel
程序员修心2 小时前
CSS 盒子模型与布局核心知识点总结
开发语言·前端·javascript
C语言小火车2 小时前
【C++】从零开始构建C++停车场管理系统:技术详解与实战指南
开发语言·c++·毕业设计·课程设计