7、格式化读写函数:fscanf与fprintf函数详解

好,咱们今天聊聊格式化读写。说白了,就是带格式的输入输出。

你平时用 printfscanf 跟控制台打交道,对吧?那如果我想把同样的格式化数据写到文件里,或者从文件里读出来呢?这时候就该 fprintffscanf 登场了。

7.1 函数原型与参数

先看原型,其实特别简单:

#include <stdio.h>

int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);

printf/scanf 比,就多了一个参数——文件指针 FILE *stream。其他一模一样。

我个人习惯把这两个函数叫做「带文件版的格式化IO」。你想想看,printf 其实等价于 fprintf(stdout, ...)scanf 等价于 fscanf(stdin, ...)。嗯,底层就是这么干的。

函数 输出目标 输入来源
printf / scanf 标准输出(屏幕) 标准输入(键盘)
fprintf / fscanf 任意文件流 任意文件流

7.2 格式化写入示例

我直接上代码。假设我们要记录一批传感器的数据:

#include <stdio.h>

typedef struct {
    int id;
    float temperature;
    float humidity;
} SensorData;

int main() {
    FILE *fp = fopen("sensor_log.txt", "w");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }

    SensorData data[] = {
        {1, 25.6, 60.2},
        {2, 26.1, 58.7},
        {3, 24.8, 62.3}
    };

    for (int i = 0; i < 3; i++) {
        fprintf(fp, "%d %.1f %.1f\n",
                data[i].id,
                data[i].temperature,
                data[i].humidity);
    }

    fclose(fp);
    printf("数据写入完成\n");
    return 0;
}

运行后,sensor_log.txt 里的内容是这样的:

1 25.6 60.2
2 26.1 58.7
3 24.8 62.3

格式清晰,每行一条记录。我在项目中经常用这种方式做数据导出,方便后续用 Excel 或 Python 分析。

7.3 格式化读取示例

读回来也很直接:

#include <stdio.h>

int main() {
    FILE *fp = fopen("sensor_log.txt", "r");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }

    int id;
    float temp, hum;
    int count = 0;

    while (fscanf(fp, "%d %f %f", &id, &temp, &hum) == 3) {
        printf("记录%d: ID=%d, 温度=%.1f, 湿度=%.1f\n",
               ++count, id, temp, hum);
    }

    fclose(fp);
    return 0;
}

关键点:fscanf 返回成功匹配并赋值的参数个数。如果返回 3,说明三个字段都读对了。如果返回 EOF 或小于 3,说明文件结束或格式不匹配。

7.4 与 scanf/printf 的对比

我整理了一张对比表,你一看就明白:

对比维度 printf / scanf fprintf / fscanf
操作对象 标准 I/O(stdin/stdout) 任意文件流
灵活性 固定,不能重定向 可操作任意打开的文件
错误处理 通常忽略返回值 必须检查返回值
使用场景 控制台交互、调试输出 文件读写、日志记录、数据持久化
缓冲区 行缓冲(stdout)或无缓冲(stderr) 全缓冲(除非设置)

这里有个坑,我曾经踩过:fscanf 遇到空格、换行符会自动跳过,但如果你格式字符串里写错了匹配符,比如用 %d 去读一个字母,那就会匹配失败,文件指针卡在那里不动了。

避坑指南:我曾经在解析一个配置文件时,文件里混入了中文字符,fscanf 直接罢工。后来我加了个检查:每次读取后判断返回值,如果小于预期,就用 fgetc 跳过当前字符,再重试。这招叫「容错读取」,嵌入式环境里特别实用。

7.5 格式化读写的核心逻辑

下面这张图,是我自己总结的格式化读写流程,你看一眼就能记住:

格式化读写核心流程 fprintf 写入流程 1. 打开文件("w" 或 "a") 2. 按格式字符串组织数据 3. 写入文件流 4. 关闭文件 fscanf 读取流程 1. 打开文件("r") 2. 按格式字符串匹配数据 3. 检查返回值(匹配数) 4. 关闭文件

7.6 实际项目中的经验

我做了这么多年嵌入式,格式化读写用得最多的场景有两个:

  1. 配置文件解析:比如 WiFi 模块的 SSID 和密码,用 fscanf 按格式读,简单可靠。
  2. 数据日志导出:传感器采集的数据,用 fprintf 写成 CSV 格式,上位机直接读。

小技巧:如果你要写二进制数据,别用 fprintf,用 fwrite。格式化函数是给人看的,不是给机器看的。二进制读写效率高得多,但可读性差。看场景选工具。

嗯,关于格式化读写,核心就这些。记住一句话:fprintffscanf 就是 printfscanf 的文件版,多了一个文件指针参数,多了一份对返回值的敬畏心。


公众号:蓝海资料掘金营,微信deep3321