ZetCode

C vfscanf 函数

最后修改日期:2025 年 4 月 6 日

从文件中进行格式化输入是 C 编程中的一项关键技能,它能够实现精确的数据提取。vfscanf 函数提供了强大的能力,可以使用可变参数列表从文件中读取格式化数据。本教程将深入讲解 vfscanf,涵盖其语法、用法模式和安全注意事项。实际示例将演示如何有效利用此函数,同时避免文件操作中的常见陷阱。

什么是 vfscanf?

vfscanf 函数使用可变参数列表从文件流中读取格式化输入。它是 vscanf 的文件对应版本,其工作方式类似于 fscanf,但带有一个 va_list 参数。此函数在 stdarg.hstdio.h 中声明。它返回成功匹配并赋值的输入项的数量,或者在失败时返回 EOF。始终验证返回值以确保正确处理输入。

基本 vfscanf 示例

此示例演示了如何使用带有辅助函数的 vfscanf 从文件中读取三种不同的数据类型。

basic_vfscanf.c
#include <stdio.h>
#include <stdarg.h>

void read_formatted(FILE *fp, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vfscanf(fp, fmt, args);
    va_end(args);
}

int main() {
    FILE *fp = fopen("data.txt", "r");
    if (!fp) {
        perror("Failed to open file");
        return 1;
    }

    int num;
    float value;
    char text[50];

    read_formatted(fp, "%d %f %49s", &num, &value, text);
    printf("Read: %d, %.2f, %s\n", num, value, text);

    fclose(fp);
    return 0;
}

该示例展示了一个名为 read_formatted 的包装函数,该函数在内部使用 vfscanf。该函数接受格式字符串和可变参数,并将它们传递给 vfscanf。在 main 函数中,我们打开一个文件并读取一个整数、一个浮点数和一个字符串。请注意为安全起见而存在的缓冲区大小限制(49 个字符)。请务必检查文件打开是否成功并关闭文件。

读取多个记录

此示例使用循环中的 vfscanf 来处理文件中的多个结构化记录。

multi_record.c
#include <stdio.h>
#include <stdarg.h>

int read_record(FILE *fp, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    int result = vfscanf(fp, fmt, args);
    va_end(args);
    return result;
}

int main() {
    FILE *fp = fopen("records.txt", "r");
    if (!fp) {
        perror("File open failed");
        return 1;
    }

    int id;
    char name[50];
    double salary;

    while (read_record(fp, "%d %49s %lf", &id, name, &salary) == 3) {
        printf("ID: %d, Name: %s, Salary: %.2f\n", id, name, salary);
    }

    fclose(fp);
    return 0;
}

在这里,我们创建了一个 read_record 辅助函数,它返回成功读取的项数。主循环将继续,只要所有三个预期字段(ID、姓名、薪水)都正确读取。格式字符串为字符串指定了字段宽度,以防止缓冲区溢出。循环在 EOF 或无效输入格式处自动停止。

使用 vfscanf 进行错误处理

此示例演示了使用 vfscanf 进行稳健的错误处理。

error_handling.c
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>

int safe_vfscanf(FILE *fp, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    int count = vfscanf(fp, fmt, args);
    va_end(args);

    if (count == EOF) {
        if (feof(fp)) {
            fprintf(stderr, "End of file reached\n");
        } else if (ferror(fp)) {
            perror("File read error");
        }
    } else if (count == 0) {
        fprintf(stderr, "No fields matched\n");
    }

    return count;
}

int main() {
    FILE *fp = fopen("values.txt", "r");
    if (!fp) {
        perror("Failed to open file");
        return 1;
    }

    int a, b;
    if (safe_vfscanf(fp, "%d %d", &a, &b) != 2) {
        fprintf(stderr, "Failed to read two integers\n");
        fclose(fp);
        return 1;
    }

    printf("Sum: %d\n", a + b);
    fclose(fp);
    return 0;
}

safe_vfscanf 函数通过检查 EOF 条件和文件错误来增强错误报告。它区分了文件结尾和实际读取错误。主函数期望读取两个整数,并处理读取值较少的情况。这种方法比基本的 vfscanf 用法提供了更好的诊断。

读取不同的数据类型

此示例演示了如何使用 vfscanf 从文件中读取各种数据类型。

mixed_types.c
#include <stdio.h>
#include <stdarg.h>

void read_data(FILE *fp, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    if (vfscanf(fp, fmt, args) == EOF) {
        fprintf(stderr, "Read error occurred\n");
    }
    va_end(args);
}

int main() {
    FILE *fp = fopen("mixed.txt", "r");
    if (!fp) {
        perror("Failed to open file");
        return 1;
    }

    int day, month, year;
    char event[100];
    double amount;

    read_data(fp, "%d/%d/%d %99s %lf", &day, &month, &year, event, &amount);
    printf("Date: %02d/%02d/%04d\n", day, month, year);
    printf("Event: %s\n", event);
    printf("Amount: %.2f\n", amount);

    fclose(fp);
    return 0;
}

该示例读取日期(DD/MM/YYYY 格式)、事件描述和货币金额。请注意字符串输入的仔细缓冲区大小指定(99 个字符)。read_data 函数处理可变参数传递给 vfscanf。输出使用格式化来确保日期显示一致,并在需要时显示前导零。

使用 vfscanf 进行高级格式化

此示例演示了使用 vfscanf 的高级格式说明符。

advanced_formatting.c
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

void parse_config(FILE *fp, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    int result = vfscanf(fp, fmt, args);
    va_end(args);

    if (result == EOF) {
        fprintf(stderr, "Error reading configuration\n");
        exit(EXIT_FAILURE);
    }
}

int main() {
    FILE *fp = fopen("config.txt", "r");
    if (!fp) {
        perror("Cannot open config file");
        return 1;
    }

    char user[50];
    int port;
    char ip[16];
    double timeout;

    parse_config(fp, "user = %49[^\n]\n", user);
    parse_config(fp, "port = %d\n", &port);
    parse_config(fp, "ip = %15[^\n]\n", ip);
    parse_config(fp, "timeout = %lf\n", &timeout);

    printf("Configuration loaded:\n");
    printf("User: %s\n", user);
    printf("Port: %d\n", port);
    printf("IP: %s\n", ip);
    printf("Timeout: %.1f seconds\n", timeout);

    fclose(fp);
    return 0;
}

此示例解析具有特定格式要求的配置文件。%[^\n] 格式读取直到换行符的字符串,允许值中包含空格。每行配置都有必须匹配的严格格式。parse_config 函数在失败时退出程序,这可能适用于关键配置文件。仔细指定了缓冲区大小以防止溢出。

使用 vfscanf 的最佳实践

来源

C vfscanf 文档

本教程深入探讨了 vfscanf 函数,提供了从基本到高级用法的实际示例。掌握文件格式化输入对于在 C 中构建健壮的数据处理应用程序至关重要。请记住,在处理文件输入和可变参数时,始终优先考虑安全性。

作者

我叫 Jan Bodnar,是一名敬业的程序员,对编码充满热情。自 2007 年以来,我通过 1,400 多篇文章和 8 本电子书分享了我的专业知识。凭借十多年的教学经验,我致力于使编程易于理解且引人入胜。

列表 C 标准库