ZetCode

C vsscanf 函数

最后修改于 2025 年 5 月 15 日

字符串解析是 C 编程中的一项关键技能,它能让你高效地从格式化字符串中提取数据。vsscanf 函数提供了强大的、带有可变参数的字符串扫描功能。本教程将深入讲解 vsscanf,涵盖其语法、格式说明符和实际应用。通过全面的示例和最佳实践,学习如何安全有效地解析复杂的字符串数据。

什么是 vsscanf?

vsscanf 函数是 sscanf 的一个变体,它接受一个 va_list 参数而不是可变参数。它像 scanf 一样从字符串读取格式化输入,但具有可变参数列表的灵活性。此函数声明在 stdarg.h 中,在创建字符串解析的包装函数时特别有用。请始终验证输入和缓冲区大小,以防止安全漏洞。

vsscanf 基本语法

vsscanf 的函数原型是:

int vsscanf(const char *str, const char *format, va_list arg);

参数

该函数返回成功匹配并赋值的输入项的数量。返回值为 EOF 表示在任何匹配发生之前发生了读取错误。请注意,vsscanf 不会修改原始字符串。

vsscanf 简单示例

这个基本示例演示了如何使用 vsscanf 从字符串中解析整数。

simple_vsscanf.c
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

void parse_numbers(const char *str, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    
    int result = vsscanf(str, fmt, args);
    printf("Parsed %d values\n", result);
    
    va_end(args);
}

int main() {
    const char *data = "10 20 30";
    int a, b, c;
    
    parse_numbers(data, "%d %d %d", &a, &b, &c);
    printf("Values: %d, %d, %d\n", a, b, c);
    
    return 0;
}

此示例展示了一个包装函数 parse_numbers,它使用 vsscanf 从字符串中解析三个整数。va_start 宏初始化可变参数列表,然后将其传递给 vsscanf。解析后,va_end 清理参数列表。该函数返回成功解析的值的数量。

解析不同数据类型

vsscanf 可以使用适当的格式说明符处理各种数据类型。此示例演示了从字符串中解析多种数据类型。

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

void parse_data(const char *str, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    
    int count = vsscanf(str, fmt, args);
    printf("Successfully parsed %d items\n", count);
    
    va_end(args);
}

int main() {
    const char *input = "John 25 78.5 A";
    char name[20];
    int age;
    float score;
    char grade;
    
    parse_data(input, "%19s %d %f %c", &name, &age, &score, &grade);
    printf("Name: %s\nAge: %d\nScore: %.1f\nGrade: %c\n", 
           name, age, score, grade);
    
    return 0;
}

此示例解析包含姓名(字符串)、年龄(整数)、分数(浮点数)和等级(字符)的字符串。请注意,使用 %19s 通过将字符串输入限制为 19 个字符加上空终止符来防止缓冲区溢出。始终为字符串输入指定最大宽度以确保安全。该函数报告成功解析了多少项。

高级格式说明符

vsscanf 支持高级格式说明符以进行精确解析。此示例演示了使用宽度说明符和模式匹配进行扫描。

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

void advanced_parse(const char *str, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    
    int result = vsscanf(str, fmt, args);
    if (result == EOF) {
        printf("Parsing failed\n");
    } else {
        printf("Matched %d items\n", result);
    }
    
    va_end(args);
}

int main() {
    const char *log_entry = "[2023-05-15 14:30:45] ERROR: File not found";
    int year, month, day, hour, min, sec;
    char level[10], message[50];
    
    advanced_parse(log_entry, "[%d-%d-%d %d:%d:%d] %9[^:]: %49[^\n]", 
                  &year, &month, &day, &hour, &min, &sec, level, message);
    
    printf("Date: %d-%02d-%02d\nTime: %02d:%02d:%02d\n"
           "Level: %s\nMessage: %s\n", 
           year, month, day, hour, min, sec, level, message);
    
    return 0;
}

此示例使用高级说明符解析复杂的日志条目格式。%[^:] 格式匹配直到遇到冒号的所有字符,而 %[^\n] 匹配直到换行符。宽度说明符(%9%49)可防止缓冲区溢出。该函数在一个操作中处理日期、时间、日志级别和消息提取,展示了 vsscanf 强大的模式匹配能力。

vsscanf 错误处理

在解析输入时,正确的错误处理至关重要。此示例显示了如何验证 vsscanf 的结果并处理解析错误。

error_handling_vsscanf.c
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>

bool safe_parse(const char *str, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    
    int expected, actual;
    // First count the expected number of conversions
    for (expected = 0; fmt[expected]; ) {
        if (fmt[expected++] == '%' && fmt[expected] != '%' &&      
            fmt[expected] != '*') {
            expected++;
        }
    }
    
    actual = vsscanf(str, fmt, args);
    va_end(args);
    
    return actual == expected;
}

int main() {
    const char *good_input = "42 3.14 hello";
    const char *bad_input = "42 not_a_float hello";
    
    int num;
    float f;
    char str[20];
    
    if (safe_parse(good_input, "%d %f %19s", &num, &f, str)) {
        printf("Good parse: %d, %.2f, %s\n", num, f, str);
    } else {
        printf("Failed to parse good input\n");
    }
    
    if (safe_parse(bad_input, "%d %f %19s", &num, &f, str)) {
        printf("Parsed bad input (unexpected)\n");
    } else {
        printf("Correctly rejected bad input\n");
    }
    
    return 0;
}

此示例实现了一个更安全的解析函数,该函数验证预期的转换次数是否与实际次数匹配。safe_parse 函数首先计算格式字符串中的格式说明符数量,然后将其与 vsscanf 的返回值进行比较。此方法有助于检测部分或失败的解析尝试。在使用从程序中提取的值之前,请始终验证解析结果。

使用 vsscanf 构建自定义解析器

vsscanf 非常适合创建自定义解析器。此示例展示了一个使用 vsscanf 的配置文件解析器。

config_parser_vsscanf.c
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdbool.h>

bool parse_config_line(const char *line, const char *key, 
                      const char *fmt, void *value) {
    char current_key[50];
    va_list args;
    
    // First try to match the key
    if (vsscanf(line, " %49[^= \t] = %[^\n]", current_key, (char *)value) != 2) {
        return false;
    }
    
    if (strcmp(current_key, key) != 0) {
        return false;
    }
    
    // If format specified, parse the value properly
    if (fmt) {
        va_start(args, value);
        int result = vsscanf((char *)value, fmt, args);
        va_end(args);
        return result == 1;
    }
    
    return true;
}

int main() {
    const char *config = "width = 1024\nheight = 768\ntitle = My Application";
    int width, height;
    char title[50];
    
    char *line = strtok((char *)config, "\n");
    while (line) {
        if (parse_config_line(line, "width", "%d", &width)) {
            printf("Found width: %d\n", width);
        }
        else if (parse_config_line(line, "height", "%d", &height)) {
            printf("Found height: %d\n", height);
        }
        else if (parse_config_line(line, "title", "%49[^\n]", title)) {
            printf("Found title: %s\n", title);
        }
        
        line = strtok(NULL, "\n");
    }
    
    return 0;
}

此示例演示了使用 vsscanf 构建配置文件解析器。parse_config_line 函数处理由等号分隔的键值对。它首先将键和值提取为字符串,然后根据指定的格式可选地解析值。解析器支持不同的数据类型并提供灵活的配置处理。此方法可以扩展以处理更复杂的文件格式。

使用 vsscanf 的最佳实践

来源

C vsscanf 文档

本教程深入探讨了 vsscanf 函数,从基本用法到高级解析技术。掌握 vsscanf 可以让您在 C 程序中创建灵活而强大的字符串解析例程。请记住,在解析不受信任的输入时,始终优先考虑安全性。

作者

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

列表 C 标准库