C 语言文件操作进阶:格式化读写 + 二进制读写 + 随机读写进阶全解

🏠个人主页:黎雁

🎬作者简介:C/C++/JAVA后端开发学习者

❄️个人专栏:C语言数据结构(C语言)EasyX游戏规划

✨ 从来绝巘须孤往,万里同尘即玉京

文章目录

  • [C 语言文件操作进阶:格式化读写 + 二进制读写 + 随机读写进阶全解 🚀](#C 语言文件操作进阶:格式化读写 + 二进制读写 + 随机读写进阶全解 🚀)
    • [前景回顾:文件基础速记 📝](#前景回顾:文件基础速记 📝)
    • [一、格式化读写 ------ 处理复杂数据的利器 📊](#一、格式化读写 —— 处理复杂数据的利器 📊)
      • [1. 格式化输出:fprintf函数](#1. 格式化输出:fprintf函数)
      • [2. 格式化输入:fscanf函数](#2. 格式化输入:fscanf函数)
      • [3. 补充:sprintf/sscanf(字符串与格式化数据互转)](#3. 补充:sprintf/sscanf(字符串与格式化数据互转))
    • [二、二进制读写 ------ 高效存储数据的方式 ⚡](#二、二进制读写 —— 高效存储数据的方式 ⚡)
      • [1. 二进制输出:fwrite函数](#1. 二进制输出:fwrite函数)
      • [2. 二进制输入:fread函数](#2. 二进制输入:fread函数)
      • [3. 二进制读写结构体(实战常用)](#3. 二进制读写结构体(实战常用))
    • [三、随机读写 ------ 精准定位文件内容 🎯](#三、随机读写 —— 精准定位文件内容 🎯)
      • [1. 调整文件指针:fseek函数](#1. 调整文件指针:fseek函数)
      • [2. 获取偏移量:ftell函数](#2. 获取偏移量:ftell函数)
      • [3. 重置文件指针:rewind函数](#3. 重置文件指针:rewind函数)
    • [写在最后 📝](#写在最后 📝)

C 语言文件操作进阶:格式化读写 + 二进制读写 + 随机读写进阶全解 🚀

在上一篇中,我们掌握了文件操作的基础------文件的打开关闭、字符和字符串的顺序读写,这一篇我们进阶学习更实用的操作:格式化读写(轻松处理结构体等复杂数据)、二进制读写(高效存储数据)、随机读写(精准定位文件内容),让文件操作能力再上一个台阶!

前景回顾:文件基础速记 📝

C 语言文件操作入门:文件基础认知 + 打开关闭 + 字符字符串读写精讲

回顾上一篇核心知识点,是进阶学习的前提:

  1. 文件操作核心流程:打开(fopen)→ 读写 → 关闭(fclose),必须检查fopen返回值,关闭后指针置空。
  2. fputc/fgetc处理单个字符,fputs/fgets处理字符串,支持文件流和标准流(stdin/stdout)。
  3. 文本文件存储ASCII码(易读),二进制文件直接存储二进制数据(高效)。

一、格式化读写 ------ 处理复杂数据的利器 📊

字符/字符串读写只能处理简单文本,而实际开发中常需要读写数字、结构体等复杂数据,这就需要fscanf(格式化读)和fprintf(格式化写),用法和scanf/printf几乎一致,只是多了文件指针参数。

1. 格式化输出:fprintf函数

函数原型:int fprintf(FILE* stream, const char* format, ...);

  • stream:文件指针(写入文件传文件指针,写入屏幕传stdout);
  • format:格式控制符(和printf完全一致,如%d、%s、%lf等);
  • 剩余参数:要写入的变量/数据。

示例:向文件写入结构体数据

c 复制代码
#include <stdio.h>
// 定义结构体
struct Student
{
    char name[20]; // 姓名
    int age;       // 年龄
    double score;  // 分数
};

int main()
{
    struct Student s = {"张三", 18, 95.5};
    // 打开文件,以只写模式(文本文件)
    FILE* pf = fopen("student.txt", "w");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    // 格式化写入结构体数据
    fprintf(pf, "姓名:%s 年龄:%d 分数:%.1lf\n", s.name, s.age, s.score);
    // 也可以输出到屏幕(替换pf为stdout)
    fprintf(stdout, "姓名:%s 年龄:%d 分数:%.1lf\n", s.name, s.age, s.score);

    // 关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

效果:student.txt中写入姓名:张三 年龄:18 分数:95.5,同时屏幕也会输出相同内容。

2. 格式化输入:fscanf函数

函数原型:int fscanf(FILE* stream, const char* format, ...);

  • stream:文件指针(读取文件传文件指针,读取键盘传stdin);
  • format:格式控制符(和scanf完全一致);
  • 剩余参数:存储数据的变量地址(注意取地址符&)。

示例:从文件读取结构体数据

c 复制代码
#include <stdio.h>
struct Student
{
    char name[20];
    int age;
    double score;
};

int main()
{
    struct Student s = {0}; // 初始化结构体
    // 打开文件,以只读模式(文本文件)
    FILE* pf = fopen("student.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    // 格式化读取文件中的数据到结构体
    fscanf(pf, "姓名:%s 年龄:%d 分数:%lf", s.name, &(s.age), &(s.score));
    // 打印读取结果
    printf("读取到的数据:\n");
    printf("姓名:%s 年龄:%d 分数:%.1lf\n", s.name, s.age, s.score);

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

输出:读取到的数据:姓名:张三 年龄:18 分数:95.5

3. 补充:sprintf/sscanf(字符串与格式化数据互转)

这两个函数不是文件操作函数,但常和文件读写配合使用,核心作用是"格式化数据↔字符串"互转:

  • sprintf:将格式化数据转为字符串;
  • sscanf:从字符串中读取格式化数据。

示例:结构体与字符串互转

c 复制代码
#include <stdio.h>
struct S
{
    char arr[20];
    int num;
    double pai;
};

int main()
{
    struct S s = {"hello", 100, 3.14159};
    char buf[100] = {0}; // 存储转换后的字符串

    // 1. 结构体数据 → 字符串
    sprintf(buf, "%s %d %.3lf", s.arr, s.num, s.pai);
    printf("转换后的字符串:%s\n", buf); // 输出:hello 100 3.142

    // 2. 字符串 → 结构体数据
    struct S t = {0};
    sscanf(buf, "%s %d %lf", t.arr, &(t.num), &(t.pai));
    printf("转换后的结构体:%s %d %.3lf\n", t.arr, t.num, t.pai); // 输出:hello 100 3.142

    return 0;
}

二、二进制读写 ------ 高效存储数据的方式 ⚡

格式化读写是"文本形式"存储(易读但占空间、效率低),而二进制读写直接存储内存中的二进制数据,效率更高、占用空间更小,核心函数是fwrite(二进制写)和fread(二进制读)。

1. 二进制输出:fwrite函数

函数原型:size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);

  • ptr:指向要写入数据的指针(比如数组/结构体地址);
  • size:单个元素的大小(字节),常用sizeof计算;
  • count:要写入的元素个数;
  • stream:文件指针(需以二进制模式打开,如"wb");
  • 返回值:成功写入的元素个数(正常等于count)。

示例:向文件写入二进制数据(数组)

c 复制代码
#include <stdio.h>
int main()
{
    int arr[] = {1, 2, 3, 4, 5}; // 要写入的数组
    int len = sizeof(arr) / sizeof(arr[0]); // 元素个数

    // 打开文件,以二进制只写模式
    FILE* pf = fopen("data.bin", "wb");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    // 二进制写入数组(5个int元素,每个4字节)
    size_t ret = fwrite(arr, sizeof(int), len, pf);
    if (ret == len)
    {
        printf("二进制写入成功,共写入%d个元素\n", len);
    }
    else
    {
        printf("写入失败,仅写入%d个元素\n", ret);
    }

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

注意:用普通文本编辑器打开data.bin会显示乱码(二进制数据),需用二进制编辑器查看。

2. 二进制输入:fread函数

函数原型:size_t fread(void* ptr, size_t size, size_t count, FILE* stream);

  • 参数含义和fwrite完全一致;
  • stream:文件指针(需以二进制只读模式打开,如"rb");
  • 返回值:成功读取的元素个数(正常等于count,文件末尾/出错时小于count)。

示例:从文件读取二进制数据(数组)

c 复制代码
#include <stdio.h>
int main()
{
    int arr[10] = {0}; // 存储读取的数据
    int read_count = 5; // 要读取的元素个数

    // 打开文件,以二进制只读模式
    FILE* pf = fopen("data.bin", "rb");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    // 二进制读取数据到数组
    size_t ret = fread(arr, sizeof(int), read_count, pf);
    if (ret == read_count)
    {
        printf("读取成功,共读取%d个元素:", ret);
        for (int i = 0; i < ret; i++)
        {
            printf("%d ", arr[i]); // 输出:1 2 3 4 5
        }
    }
    else
    {
        printf("读取失败,仅读取%d个元素\n", ret);
    }

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

3. 二进制读写结构体(实战常用)

c 复制代码
#include <stdio.h>
struct Student
{
    char name[20];
    int age;
    double score;
};

int main()
{
    // 1. 写入结构体二进制数据
    struct Student s = {"李四", 19, 98.0};
    FILE* pf = fopen("stu.bin", "wb");
    if (pf == NULL) { perror("fopen"); return 1; }
    fwrite(&s, sizeof(struct Student), 1, pf);
    fclose(pf);
    pf = NULL;

    // 2. 读取结构体二进制数据
    struct Student t = {0};
    pf = fopen("stu.bin", "rb");
    if (pf == NULL) { perror("fopen"); return 1; }
    fread(&t, sizeof(struct Student), 1, pf);
    printf("姓名:%s 年龄:%d 分数:%.1lf\n", t.name, t.age, t.score); // 输出:李四 19 98.0

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

三、随机读写 ------ 精准定位文件内容 🎯

顺序读写只能从文件开头到末尾依次操作,而随机读写可以通过函数调整文件指针位置,直接读写指定位置的内容,核心函数是fseekftellrewind

1. 调整文件指针:fseek函数

函数原型:int fseek(FILE* stream, long int offset, int origin);

  • stream:文件指针;
  • offset:偏移量(正数向后偏移,负数向前偏移);
  • origin:起始位置(3个可选值):
    • SEEK_SET:文件开头位置(常用);
    • SEEK_CUR:文件指针当前位置;
    • SEEK_END:文件末尾位置;
  • 返回值:调整成功返回0,失败返回非0。

示例:定位读取文件内容

c 复制代码
#include <stdio.h>
int main()
{
    // 假设data.txt内容为:abcdefghijk
    FILE* pf = fopen("data.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    // 1. 从文件开头偏移3个字符(定位到'd')
    fseek(pf, 3, SEEK_SET);
    int ch = fgetc(pf);
    printf("读取到的字符:%c\n", ch); // 输出:d

    // 2. 从当前位置(d)向后偏移2个字符(定位到'f')
    fseek(pf, 2, SEEK_CUR);
    ch = fgetc(pf);
    printf("读取到的字符:%c\n", ch); // 输出:f

    // 3. 从文件末尾向前偏移4个字符(定位到'h')
    fseek(pf, -4, SEEK_END);
    ch = fgetc(pf);
    printf("读取到的字符:%c\n", ch); // 输出:h

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

2. 获取偏移量:ftell函数

函数原型:long int ftell(FILE* stream);

  • 功能:返回文件指针相对于文件开头的偏移量(字节数);
  • 用途:配合fseek使用,确认指针位置。

示例:查看文件指针偏移量

c 复制代码
#include <stdio.h>
int main()
{
    FILE* pf = fopen("data.txt", "r");
    if (pf == NULL) { perror("fopen"); return 1; }

    // 读取第一个字符
    fgetc(pf);
    // 获取当前偏移量(已读取1个字符,偏移量为1)
    long pos = ftell(pf);
    printf("当前文件指针偏移量:%ld\n", pos); // 输出:1

    // 调整指针到开头偏移5的位置
    fseek(pf, 5, SEEK_SET);
    pos = ftell(pf);
    printf("调整后偏移量:%ld\n", pos); // 输出:5

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

3. 重置文件指针:rewind函数

函数原型:void rewind(FILE* stream);

  • 功能:将文件指针重置到文件开头位置;
  • 用途:快速回到文件开头,无需手动计算偏移量。

示例:重置文件指针

c 复制代码
#include <stdio.h>
int main()
{
    FILE* pf = fopen("data.txt", "r");
    if (pf == NULL) { perror("fopen"); return 1; }

    // 读取前3个字符
    for (int i = 0; i < 3; i++)
    {
        printf("%c ", fgetc(pf)); // 输出:a b c
    }

    // 重置指针到开头
    rewind(pf);
    printf("\n重置后读取第一个字符:%c\n", fgetc(pf)); // 输出:a

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

写在最后 📝

这一篇我们掌握了文件操作的进阶技能:用fscanf/fprintf处理格式化数据(适配结构体等复杂类型),用fread/fwrite实现高效的二进制读写,用fseek/ftell/rewind完成精准的随机读写。这些是实际开发中最常用的文件操作方式,覆盖了大部分场景的需求。

核心要点总结

  1. 格式化读写(fscanf/fprintf):兼容复杂数据类型,用法和scanf/printf一致,多了文件指针参数。
  2. 二进制读写(fread/fwrite):高效存储,需以二进制模式(rb/wb)打开文件,适合批量/结构体数据。
  3. 随机读写核心:fseek调整指针位置,ftell查看偏移量,rewind重置到开头,摆脱顺序读写限制。

下一篇我们将解决文件操作的坑------文件读取结束的判定、文件缓冲区的原理,让你的文件操作代码更健壮!

相关推荐
talenteddriver2 小时前
web: jwt令牌构成、创建的基本流程及原理
java·开发语言·python·网络协议·web
这周也會开心2 小时前
双栈实现队列以及双队列实现栈
java·开发语言
Bruce_kaizy2 小时前
c++图论——最短路之Johnson算法
开发语言·数据结构·c++·算法·图论
“抚琴”的人2 小时前
C#上位机观察者模式
开发语言·观察者模式·c#·上位机
思成Codes2 小时前
Go语言的多返回值是如何实现的?
开发语言·后端·golang
北极糊的狐2 小时前
MQTT报错:Exception in thread main java.lang.at io.github.pnoker.common.sdk.utils.ParseUtils.decodeHex
java·开发语言
CQ_YM2 小时前
网络编程之UDP
linux·c语言·网络·单片机·udp
小刘爱玩单片机2 小时前
【stm32简单外设篇】- ESP8266 Wi-Fi 模块(ESP-01系列)
c语言·stm32·单片机·嵌入式硬件
Grassto2 小时前
Go 是如何解析 `import path` 的?第三方包定位原理
开发语言·golang·go module·go import