fscanf函数如何正确读取文件数据?

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 C语言 正文

fscanf 是什么?

fscanf 是 C 标准库中的一个函数,它的名字来源于 "file scan formatted"(文件格式化扫描),它的作用类似于 scanf,但有一个关键区别:

c语言fscanf函数用法
(图片来源网络,侵删)
  • scanf: 从 标准输入流(通常是键盘)读取格式化数据。
  • fscanf: 从 指定的文件流(通常是文件)读取格式化数据。

fscanf 就是在文件上执行 scanf 操作。


函数原型

stdio.h 头文件中,fscanf 的原型如下:

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

参数详解

  1. FILE *stream:

    • 这是一个指向 FILE 结构体的指针,也就是我们常说的“文件指针”或“文件流”。
    • 它代表了你要从哪个文件中读取数据,这个文件指针通常是由 fopen() 函数打开文件后返回的。
    • FILE *fp = fopen("data.txt", "r");fscanf 的第一个参数就可以是 fp
  2. const char *format:

    c语言fscanf函数用法
    (图片来源网络,侵删)
    • 这是一个格式化字符串,它定义了你期望读取的数据的格式。
    • 它的用法和 scanf 中的格式化字符串完全一样。
    • 可以包含:
      • 格式说明符: 如 %d (整数), %f (浮点数), %s (字符串), %c (字符) 等。
      • 普通字符: 空格、制表符、换行符等空白字符会匹配并跳过输入流中的连续空白字符,非空白字符则会精确匹配输入流中的对应字符。
      • 修饰符: 如 %hd (短整型), %lf (双精度浮点数) 等。
  3. (可变参数):

    • 这是 ,表示这是一个可变参数函数。
    • 你需要提供一系列指针,用于存储从文件中读取到的数据。
    • 非常重要: 这些参数必须是指针(变量的地址),因为 fscanf 需要通过指针将读取到的值存回到你指定的内存位置。
    • 如果你要读取一个整数,你需要提供一个整型变量的地址 &my_int

返回值

fscanf 函数返回一个 int 类型的值,这个值有三种可能:

  1. 成功读取的项数:

    • 如果函数成功执行,它会返回成功匹配并赋值的参数的个数。
    • fscanf(fp, "%d %f", &i, &f); 如果成功读取了一个整数和一个浮点数,它会返回 2
  2. EOF (End-File):

    c语言fscanf函数用法
    (图片来源网络,侵删)
    • 当函数读取到文件末尾,或者发生读取错误时,它会返回 EOF (通常定义为 -1)。
    • 这意味着文件中没有更多的数据可以读取了。
  3. 部分匹配:

    • 如果在读取过程中,输入流的数据与格式字符串不匹配,函数会立即停止,并已经成功匹配的项数
    • 格式字符串是 %d %s,但文件内容是 "abc 123"%d 无法匹配 "abc",所以函数会停止,返回 0 (因为没有任何项被成功赋值)。

基本用法示例

下面我们通过几个从简单到复杂的例子来理解 fscanf

示例 1:读取单个整数

假设我们有一个文件 numbers.txt如下:

100

代码:

#include <stdio.h>
int main() {
    FILE *fp;
    int number;
    // 以只读模式打开文件
    fp = fopen("numbers.txt", "r");
    if (fp == NULL) {
        perror("无法打开文件");
        return 1;
    }
    // 从文件中读取一个整数,并存入 number 变量
    int result = fscanf(fp, "%d", &number);
    if (result == 1) {
        printf("成功从文件读取到整数: %d\n", number);
    } else {
        printf("读取失败或文件格式不正确,\n");
    }
    // 关闭文件
    fclose(fp);
    return 0;
}

输出:

成功从文件读取到整数: 100

示例 2:读取多个不同类型的数据

假设文件 data.txt 内容如下:

John Doe 25 95.5

代码:

#include <stdio.h>
#include <string.h> // 为了使用 strlen
int main() {
    FILE *fp;
    char first_name[50], last_name[50];
    int age;
    float score;
    fp = fopen("data.txt", "r");
    if (fp == NULL) {
        perror("无法打开文件");
        return 1;
    }
    // 格式说明符中的空格会跳过输入中的任意空白字符
    // %s 遇到空白字符会停止
    int result = fscanf(fp, "%s %s %d %f", first_name, last_name, &age, &score);
    if (result == 4) {
        printf("姓名: %s %s\n", first_name, last_name);
        printf("年龄: %d\n", age);
        printf("分数: %.2f\n", score);
    } else {
        printf("读取失败或文件格式不正确,返回值: %d\n", result);
    }
    fclose(fp);
    return 0;
}

输出:

姓名: John Doe
年龄: 25
分数: 95.50

示例 3:读取多行数据(使用循环)

这是 fscanf 最常见的用法之一:逐行处理文件。

假设文件 students.txt 内容如下:

Alice 20 88.0
Bob 21 92.5
Charlie 22 76.5

代码:

#include <stdio.h>
int main() {
    FILE *fp;
    char name[50];
    int age;
    float score;
    fp = fopen("students.txt", "r");
    if (fp == NULL) {
        perror("无法打开文件");
        return 1;
    }
    printf("学生信息列表:\n");
    printf("--------------------\n");
    // 循环读取,直到 fscanf 返回 EOF (即 -1)
    while (fscanf(fp, "%s %d %f", name, &age, &score) == 3) {
        printf("姓名: %-10s 年龄: %2d  分数: %5.1f\n", name, age, score);
    }
    printf("--------------------\n");
    printf("文件读取完毕,\n");
    fclose(fp);
    return 0;
}

输出:

学生信息列表:
--------------------
姓名: Alice      年龄: 20  分数: 88.0
姓名: Bob        年龄: 21  分数: 92.5
姓名: Charlie    年龄: 22  分数: 76.5
--------------------
文件读取完毕。

重要注意事项和常见错误

  1. 文件指针必须有效:

    • 在使用 fscanf 之前,必须先用 fopen 成功打开文件,并检查其返回值是否为 NULL,操作未打开或打开失败的文件是未定义行为。
  2. 参数必须是地址:

    • 这是初学者最容易犯的错误,你必须传递变量的地址,而不是变量本身。
    • 错误: fscanf(fp, "%d", number);
    • 正确: fscanf(fp, "%d", &number);
  3. 格式说明符与数据类型必须匹配:

    • 格式字符串中的 %d 必须对应一个 int*%f 必须对应一个 float*%lf (用于 double) 必须对应一个 double*
    • 一个非常经典的错误是读取 double 类型时使用 %f
      double my_double;
      // 错误:应该使用 %lf
      fscanf(fp, "%f", &my_double); // 可能导致读取不正确或警告
      // 正确
      fscanf(fp, "%lf", &my_double);
  4. 空白字符的处理:

    • 在格式字符串中,一个或多个连续的空格、制表符(\t)、换行符(\n)会匹配输入流中的任意数量的连续空白字符。
    • 这使得 fscanf(fp, "%d %d", &a, &b); 能够正确处理 "10 20", "10 20", "10\n20" 等多种情况。
  5. %s 的陷阱:

    • %s 格式说明符会读取连续的非空白字符,直到遇到下一个空白字符为止。
    • 它不会自动在读取的字符串末尾添加空字符 \0fscanf 会假设你提供的字符数组足够大,并会自动在末尾添加 \0
    • 确保你的目标数组足够大,以防止缓冲区溢出,定义 char name[50]; 来存储一个名字,通常是比较安全的。
  6. 检查返回值:

    • 总是检查 fscanf 的返回值,这是判断操作是否成功以及是否到达文件末尾的唯一可靠方法,不要盲目地假设数据一定存在。

fscanf vs. fgets + sscanf

在读取结构化文本文件时,fscanf 虽然方便,但也有其局限性,如果一行中的数据格式错乱(比如本应是数字的地方是字母),fscanf 可能会“卡住”或跳过整行。

一个更稳健的替代方案是结合使用 fgetssscanf

  1. fgets: 从文件中读取一整行文本到一个字符串中。
  2. sscanf: 从一个字符串中读取格式化数据。

这种方法的好处是,即使一行数据格式错误,你的程序也能继续处理下一行,而不是轻易崩溃。

示例对比

假设文件 bad_data.txt 内容如下:

Alice 20 90.0
Bob is not a number 22 85.0
Charlie 23 95.0

使用 fscanf 的风险:

// ... (打开文件) ...
while (fscanf(fp, "%s %d %f", name, &age, &score) == 3) {
    // 当读到 "Bob is not a number..." 这一行时
    // fscanf 会尝试用 "%s" 读取 "Bob",然后发现 "is" 不是数字,匹配失败。
    // 它可能会返回 1 (只成功读了 name),或者直接进入未定义状态。
    // 循环可能会变得不可预测。
}

使用 fgets + sscanf 的稳健方法:

#include <stdio.h>
#include <string.h>
#define MAX_LINE_LENGTH 256
int main() {
    FILE *fp;
    char line[MAX_LINE_LENGTH];
    char name[50];
    int age;
    float score;
    fp = fopen("bad_data.txt", "r");
    if (fp == NULL) {
        perror("无法打开文件");
        return 1;
    }
    while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) {
        // 尝试从刚刚读取的行中解析数据
        int items_read = sscanf(line, "%s %d %f", name, &age, &score);
        if (items_read == 3) {
            printf("成功解析: 姓名: %s, 年龄: %d, 分数: %.1f\n", name, age, score);
        } else {
            printf("跳过格式错误的行: %s", line);
        }
    }
    fclose(fp);
    return 0;
}

输出:

成功解析: 姓名: Alice, 年龄: 20, 分数: 90.0
跳过格式错误的行: Bob is not a number 22 85.0
成功解析: 姓名: Charlie, 年龄: 23, 分数: 95.0
特性 fscanf fgets + sscanf
读取单位 逐项读取(根据格式) 逐行读取
优点 代码简洁,直接 非常稳健,易于处理错误行
缺点 容易因格式错误而“卡住” 代码稍长,需要两步操作
适用场景 数据格式非常规范、可靠,且追求代码简洁 需要处理可能包含错误或不规范格式的文件

fscanf 是一个功能强大且方便的函数,特别适合处理格式非常严格的文件,但在实际应用中,尤其是在处理用户输入或不可控来源的文件时,fgets + sscanf 的组合通常被认为是更安全、更稳健的选择。

-- 展开阅读全文 --
头像
dede汽车网站模板怎么用?
« 上一篇 03-10
sober算子C语言代码如何实现?
下一篇 » 03-10

相关文章

取消
微信二维码
支付宝二维码

目录[+]