关注

【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*类型的指针来操作这个结构体,这个指针就是文件指针

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

2. 打开与关闭函数

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

// 打开文件:返回指向文件的指针,失败则返回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+”读写文本文件创建新文件读写均从文件末尾开始

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

#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 顺序读写:按文件顺序依次操作

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

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

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

#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)

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

#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)

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

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

#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语言提供了三个核心函数实现位置定位:

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

关键参数说明

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

随机读写示例

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

#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字节)
适用场景配置文件、日志文件结构体数据、图片、音频

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

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

#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. 程序正常结束时,所有打开的文件缓冲区会自动刷新。

缓冲区演示

#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;
}

在这里插入图片描述


🚩总结

请添加图片描述

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/a_hong_sen/article/details/155754792

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--