【C语言】 C语言文件操作

文章目录


一、文件的核心概念

1. 什么是文件

文件是存储在外部存储介质(如磁盘、U盘)上的一组相关数据的集合。我们日常的文档编辑、程序运行、数据备份等操作,本质上都是对文件的读写和修改。在C语言中,文件主要分为两类:

  • 程序文件:与程序运行相关的文件,包括源程序文件(.c后缀)、编译后的目标文件(Windows下为.obj)、可执行文件(Windows下为.exe)。
  • 数据文件:程序运行时需要读取或写入的数据载体,比如存储用户信息的文本文件、保存图片的二进制文件等,这也是本文的重点讨论对象。

2. 文件名的组成

一个完整的文件名是文件的唯一标识,由三部分组成:文件路径 + 文件名 + 文件后缀,例如:

  • 绝对路径:D:\project\student.txt
  • 相对路径:../data/config.bin../表示上一级目录)
  • 当前路径:log.txt(直接写文件名,默认在程序运行目录下)

二、文件的打开与关闭

文件操作的第一步是建立程序与文件的连接,这就需要通过"打开文件"操作实现;操作完成后必须"关闭文件",释放系统资源,避免内存泄漏。

1. 文件指针

缓冲文件系统中,系统会为每个打开的文件分配一块内存区域(文件信息区),用于存储文件名、状态、当前读写位置等关键信息。这个信息区被封装为FILE结构体(定义在<stdio.h>中),而我们通过FILE*类型的指针来操作这个结构体,这个指针就是文件指针

c 复制代码
#include <stdio.h>
// 定义文件指针变量,用于指向文件信息区
FILE* pf;

2. 打开与关闭函数

C语言标准规定使用fopen函数打开文件,fclose函数关闭文件,函数原型如下:

c 复制代码
// 打开文件:返回指向文件的指针,失败则返回NULL
FILE* fopen(const char* filename, const char* mode);
// 关闭文件:成功返回0,失败返回EOF(-1)
int fclose(FILE* stream);

关键:文件打开模式

文件打开模式决定了程序对文件的操作权限,不同模式对应不同的功能和行为,下表汇总了常用模式的核心信息:

打开模式 核心功能 适用文件类型 若文件不存在 注意事项
"r" 只读 文本文件 打开失败(返回NULL) 只能读取已有文件
"w" 只写 文本文件 创建新文件 会覆盖原有文件内容
"a" 追加 文本文件 打开失败 仅在文件末尾添加数据
"rb" 只读 二进制文件 打开失败 用于读取图片、音频等资源
"wb" 只写 二进制文件 创建新文件 二进制形式存储数据
"r+" 读写 文本文件 打开失败 可同时读写,不创建新文件
"w+" 读写 文本文件 创建新文件 覆盖原有内容,支持读写
"a+" 读写 文本文件 创建新文件 读写均从文件末尾开始

实战:文件打开与关闭示例

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 以只写模式打开当前目录下的data.txt,不存在则创建
    FILE* pf = fopen("data.txt", "w");
    
    // 关键:检查文件是否打开成功(路径错误、权限不足等会导致失败)
    if (pf == NULL) {
        // perror函数会自动输出错误原因
        perror("fopen failed");
        // 打开失败直接退出程序
        return EXIT_FAILURE;
    }

    // 后续文件操作...

    // 关闭文件:必须执行,避免资源泄露
    fclose(pf);
    // 置空指针:防止野指针操作
    pf = NULL;

    return 0;
}

三、文件的读写操作

文件读写是文件操作的核心,C语言提供了多种读写函数,适用于不同的数据类型和场景,主要分为顺序读写随机读写两类。

1 顺序读写:按文件顺序依次操作

顺序读写是最常用的方式,数据从文件开头到结尾依次读取或写入,常用函数如下:

功能类型 函数名 函数原型 适用场景
字符输入 fgetc int fgetc(FILE* stream) 读取单个字符
字符输出 fputc int fputc(int c, FILE* stream) 写入单个字符
行输入 fgets char* fgets(char* str, int num, FILE* stream) 读取一行字符串
行输出 fputs int fputs(const char* str, FILE* stream) 写入一行字符串
格式化输入 fscanf int fscanf(FILE* stream, const char* format, ...) 按格式读取数据
格式化输出 fprintf int fprintf(FILE* stream, const char* format, ...) 按格式写入数据
二进制输入 fread size_t fread(void* ptr, size_t size, size_t count, FILE* stream) 读取二进制数据
二进制输出 fwrite size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream) 写入二进制数据
3.1 字符级读写(fputc/fgetc)

需求:向文件写入26个大写字母,再读取并打印。

c 复制代码
#include <stdio.h>

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

    // 写入A-Z
    for (char c = 'A'; c <= 'Z'; c++) {
        // 逐个字符写入文件
        fputc(c, pf);
    }
    fclose(pf);
    pf = NULL;

    // 读取并打印
    pf = fopen("letter.txt", "r");
    if (pf == NULL) {
        perror("fopen read failed");
        return 1;
    }

    int ch;
    // EOF是文件结束标志(值为-1)
    while ((ch = fgetc(pf)) != EOF) {
        printf("%c ", ch);
    }
    fclose(pf);
    pf = NULL;

    return 0;
}
3.2 格式化读写(fprintf/fscanf)

需求:将用户信息(学号、成绩)写入文件,再读取并显示。

c 复制代码
#include <stdio.h>

typedef struct {
    int id;
    float score;
} Student;

int main() {
    Student stu = {101, 92.5f};
    FILE* pf = fopen("student.txt", "w");
    if (pf == NULL) {
        perror("fopen failed");
        return 1;
    }

    // 格式化写入结构体数据
    fprintf(pf, "学号:%d 成绩:%.1f", stu.id, stu.score);
    fclose(pf);
    pf = NULL;

    // 读取数据
    Student tmp;
    pf = fopen("student.txt", "r");
    if (pf == NULL) {
        perror("fopen failed");
        return 1;
    }

    // 按对应格式读取
    fscanf(pf, "学号:%d 成绩:%.1f", &tmp.id, &tmp.score);
    printf("读取到的信息:学号=%d,成绩=%.1f\n", tmp.id, tmp.score);
    fclose(pf);
    pf = NULL;

    return 0;
}
3.3 二进制读写(fwrite/fread)

二进制读写适用于存储结构体、图片、音频等数据,直接以内存中的二进制形式存储,效率更高。

需求:将结构体数据以二进制形式存储,再读取恢复。

c 复制代码
#include <stdio.h>

typedef struct {
    int id;
    char name[20];
    float score;
} Student;

int main() {
    Student stu = {102, "Zhang San", 88.0f};
    FILE* pf = fopen("student.bin", "wb");
    if (pf == NULL) {
        perror("fopen failed");
        return 1;
    }

    // 二进制写入:1个大小为Student的结构体数据
    fwrite(&stu, sizeof(Student), 1, pf);
    fclose(pf);
    pf = NULL;

    // 二进制读取
    Student tmp = {0};
    pf = fopen("student.bin", "rb");
    if (pf == NULL) {
        perror("fopen failed");
        return 1;
    }

    fread(&tmp, sizeof(Student), 1, pf);
    printf("ID:%d,姓名:%s,成绩:%.1f\n", tmp.id, tmp.name, tmp.score);
    fclose(pf);
    pf = NULL;

    return 0;
}

注意:二进制文件用文本编辑器打开会显示乱码,这是正常现象,需通过程序按二进制方式读取才能恢复数据。

2. 随机读写:自由定位读写位置

有时我们需要直接操作文件中间的数据(如修改文件第10个字符、读取第5条记录),这就需要随机读写功能。C语言提供了三个核心函数实现位置定位:

函数名 函数原型 功能描述
fseek int fseek(FILE* stream, long offset, int origin) 定位文件指针位置
ftell long ftell(FILE* stream) 获取文件指针当前偏移量
rewind void rewind(FILE* stream) 将文件指针重置到文件开头

关键参数说明

  • offset:偏移量(正数表示向后偏移,负数表示向前偏移)
  • origin:基准位置,有三个可选值:
    • SEEK_SET:以文件开头为基准(值为0)
    • SEEK_CUR:以当前指针位置为基准(值为1)
    • SEEK_END:以文件末尾为基准(值为2)

随机读写示例

需求:修改文件中指定位置的字符,读取文件中间数据。

c 复制代码
#include <stdio.h>

int main() {
    // 先写入初始数据:abcdefghi
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL) {
        perror("fopen failed");
        return 1;
    }
    fputs("abcdefghi", pf);
    fclose(pf);
    pf = NULL;

    // 随机修改:将第6个字符('f')改为'X'
    pf = fopen("test.txt", "r+");
    if (pf == NULL) {
        perror("fopen failed");
        return 1;
    }

    // 基准:文件开头,偏移5个位置(指向第6个字符)
    fseek(pf, 5, SEEK_SET);
    // 写入修改后的字符
    fputc('X', pf);

    // 定位到文件开头,读取修改后的数据
    rewind(pf);
    int ch;
    printf("修改后的数据:");
    while ((ch = fgetc(pf)) != EOF) {
        printf("%c", ch);
    }
    printf("\n");

    // 获取当前指针偏移量(此时在文件末尾)
    long pos = ftell(pf);
    printf("文件长度:%ld 字节\n", pos);

    fclose(pf);
    pf = NULL;

    return 0;
}

输出结果:

复制代码
修改后的数据:abcdeXghi
文件长度:9 字节

三、文本文件与二进制文件的区别

数据在内存中始终以二进制形式存储,而文件的存储形式由我们的操作决定:

  • 文本文件:数据存储前会转换为ASCII码形式,例如整数10000会转换为字符'1'、'0'、'0'、'0'、'0'存储,可被文本编辑器直接读取。
  • 二进制文件 :数据直接以内存中的二进制形式存储,例如整数10000在32位系统中存储为0x00002710,文本编辑器打开会显示乱码,但程序读取效率更高。

关键区别对比

特性 文本文件 二进制文件
存储形式 ASCII码 二进制数据
可读性 可直接用记事本打开 需专用程序读取
存储效率 较低(如10000占5字节) 较高(如10000占4字节)
适用场景 配置文件、日志文件 结构体数据、图片、音频

四、文件拷贝实战:综合应用

文件拷贝是文件操作的经典场景,核心逻辑是"读取源文件数据 → 写入目标文件",下面实现一个通用的文件拷贝工具:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 拷贝函数:src_path-源文件路径,dest_path-目标文件路径
int copy_file(const char* src_path, const char* dest_path) {
    // 打开源文件(只读)和目标文件(只写)
    FILE* src = fopen(src_path, "rb");
    if (src == NULL) {
        perror("open source file failed");
        return 1;
    }

    FILE* dest = fopen(dest_path, "wb");
    if (dest == NULL) {
        perror("open destination file failed");
        // 关闭已打开的源文件,避免资源泄露
        fclose(src);
        return 1;
    }

    // 用缓冲区提高拷贝效率(一次读取1024字节)
    char buf[1024];
    size_t read_len;
    // 循环读取源文件,直到文件结束
    while ((read_len = fread(buf, 1, sizeof(buf), src)) > 0) {
        // 将读取到的数据写入目标文件
        fwrite(buf, 1, read_len, dest);
    }

    // 关闭文件
    fclose(src);
    fclose(dest);
    printf("文件拷贝成功!\n");
    return 0;
}

int main() {
    // 拷贝文本文件或二进制文件均可
    copy_file("source.jpg", "target.jpg");
    return 0;
}

五、文件缓冲区机制

C语言采用"缓冲文件系统",系统会为每个打开的文件在内存中分配一块缓冲区

  • 写入数据时:数据先存入缓冲区,缓冲区满后才会一次性写入磁盘;
  • 读取数据时:先从磁盘读取数据填充缓冲区,程序再从缓冲区获取数据。

缓冲区的刷新机制

  1. 缓冲区满时自动刷新;
  2. 调用fclose关闭文件时,会自动刷新缓冲区;
  3. 调用fflush(FILE* stream)函数强制刷新缓冲区(注意:高版本VS中fflush仅对输出流有效);
  4. 程序正常结束时,所有打开的文件缓冲区会自动刷新。

缓冲区演示

c 复制代码
#include <stdio.h>
#include <windows.h>

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

    // 写入数据(此时数据在缓冲区,文件中无内容)
    fputs("hello buffer", pf);
    printf("数据已写入缓冲区,睡眠10秒...\n");
    // 睡眠10秒,期间打开buffer.txt会发现无内容
    Sleep(10000);

    // 强制刷新缓冲区,数据写入磁盘
    fflush(pf);
    printf("缓冲区已刷新,再睡眠10秒...\n");
    // 此时打开buffer.txt可看到内容
    Sleep(10000);

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

🚩总结

相关推荐
爬山算法2 小时前
Netty(5)Netty的ByteBuf是什么?它与Java NIO的ByteBuffer有何不同?
java·开发语言·nio
司徒轩宇2 小时前
C++ 内存分配详解
开发语言·c++
JH30732 小时前
Java 是值传递:深入理解参数传递机制
java·开发语言·windows
️停云️2 小时前
C++类型转换、IO流与特殊类的设计
c语言·开发语言·c++
while(1){yan}2 小时前
文件IO的常识
java·开发语言·青少年编程·电脑常识
进击的荆棘2 小时前
C++起始之路——类和对象(下)
开发语言·c++
帅得不敢出门3 小时前
精简Android SDK(AOSP)的git项目提高git指令速度
android·java·开发语言·git·elasticsearch
liu****3 小时前
10.排序
c语言·开发语言·数据结构·c++·算法·排序算法
爱写Bug的小孙3 小时前
Tools、MCP 和 Function Calling
开发语言·人工智能·python·ai·ai编程·工具调用