使用sscanf将"0x0"解析为两个数字

3
我编写了一个通用函数,用于解析配置字符串中的双精度浮点数值。当需要读取两个值时,提供解析包含这两个值的文本行的函数一个定界字符串delim_str,以知道如何分隔这两个值。我首先通过sprintf(fmt, "%%lg%s%%lg", delim_str)创建格式字符串fmt,然后使用它进行解析,即sscanf(input_string, fmt, &v1, &v2)
当我使用delim_str例如" - "", "时一切正常,但是当我尝试使用它来解析屏幕分辨率时,使用"x"作为分隔符会出现问题。它可以将输入字符串1920x1080正确解析为两个值,但如果第一个值为0,则0x1080会给我一个v1值为4224(将0x1080解释为十六进制值),而v2不会被修改。
我可以考虑使用strstr在输入字符串中查找delim_str的起始位置,然后使用它创建两个新字符串,每个变量对应一个字符串,但我感觉应该有更优雅的方法。

3
如果你想的话,你可以使用 strtok - kiran Biradar
3
你为什么要使用%lg来解析整数而不是%d呢? - melpomene
1
请注意,%i 可以扫描十六进制和八进制数,但 %d 不能。 - Weather Vane
@melpomene 哦,对了,也许我应该提一下,这个函数是相当通用的,因此我没有为解析 double 和 int 数据类型编写单独的函数,所以我使用 double 作为所有数字的公共分母。因此,该函数读取并返回 double,但调用方可以自由地进行强制转换。只要整数在 2^52 以下,这种做法基本不会有什么副作用。 - Michel Rouzic
3个回答

2
你可以使用strtok/strtod组合:使用strtok获取两个字符串,然后使用strtod将其转换为所需的double格式。
代码示例如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {

    char input_string[] = "0x1080";
    char delim_str[] = "x";


    char *v1str = NULL;
    char *v2str = NULL;

    v1str = strtok(input_string, delim_str);
    if(v1str != NULL) {
        v2str = strtok(NULL, delim_str);
    }

    if(!v1str || !v2str) {
        fprintf(stderr, "format error");
        exit(1);
    }

    double v1 = strtod(v1str, NULL);
    double v2 = strtod(v2str, NULL);
    printf("%f %f", v1, v2);


    return 0;
}

1
如果你想使用x作为浮点数的分隔符,那么你不能使用%g%lg来解析字符串:这些浮点数的转换格式符会将输入解析为strtod(),而strtod自c99以来就接受十六进制浮点语法。
7.22.1.3 strtod、strtof 和 strtold 函数
主题序列的预期形式为可选的加号或减号,然后是以下之一: - 一个非空的十进制数字序列,可选地包含小数点字符,然后是在6.4.4.2中定义的可选指数部分; - "0x" 或 "0X",然后是一个非空的十六进制数字序列,可选地包含小数点字符,然后是在6.4.4.2中定义的可选二进制指数部分; - "INF" 或 "INFINITY"(忽略大小写) - "NAN" 或 "NAN(n-char-sequenceopt)",其中"NAN"部分不区分大小写...
这是C标准先前版本的语义变化,导致像"0x0"这样的输入被解析为单个浮点值。
最简单的解决方案是使用%d将数字解析为int变量,如果它们是整数并且相对较小。但需要注意,使用sprintf组合格式字符串是有风险的:
  • 如果delim_str太长,sprintf可能会导致缓冲区溢出。应改用snprintf()
  • 如果delim_str包含嵌入的%字符,则传递给scanf()的结果格式字符串可能会导致未定义的行为。
更安全的方法是使用strstr在输入中定位分隔符字符串,并将输入拆分成子字符串传递给strtod()
以下是一个示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char input[] = "0x1080";
    const char *delim_str = "x";
    char *p;
    double v1 = 0;
    double v2 = 0;

    if ((p = strstr(input, delim_str)) != NULL) {
        *p = '\0';
        v1 = strtod(input, NULL);
        *p = *delim_str;
        v2 = strtod(p + strlen(delim_str), NULL);
        printf("%g %g\n", v1, v2);
    } else {
        printf("missing delimiter: %s\n", input);
    }
    return 0;
}

如果无法修改输入字符串,则必须制作初始部分的副本。

1
我喜欢你没有使用strtok,因为我不喜欢让我的代码变得无法重入而毫无意义。 - Michel Rouzic
@MichelRouzic 还要注意可以使用 strtok_r - S.S. Anne
@JL2210:可以使用strtok_r,但它的语义除了空格以外很容易混淆。strstr更精确,可以用于任何字符串的完全匹配。使用strtok_r", "将检测任何连续的' '','字符序列作为单个分隔符。 - chqrlie
strtok_r 是 POSIX 函数,不是标准的 C 语言函数。 - Michel Rouzic

0

我刚刚想起来我已经有一个函数在我的库中,可以用来隔离子字符串,尽管作为一个通用的答案,这可能有点过度解决了,因为这个具体的问题可以用更少的代码解决。它根据给定的分隔符查找字符串中的第N个字段:

int string_get_field(char *string, char *delim, int n, char *field) // copies the Nth field (0 indexed) of string into field
{
    int i;
    size_t delim_len = strlen(delim);
    char *end;

    // Find field start
    for (i=0; i < n; i++)
    {
        string = strstr(string, delim);         // look for the next delimiter

        if (string==NULL)               // if the next delimiter needed isn't found
            return 0;               // 0 means failure to find the field

        string += delim_len;                // set string to right after the delimiter that indicates the start
    }

    // Find field end
    end = strstr(string, delim);                // look for the next delimiter that marks the end of the field

    // Copy field
    if (end==NULL)                      // if it was the last field
        strcpy(field, string);              // copy all that is left
    else                            // otherwise
    {
        snprintf(field, end-string, "%s", string);  // only copy what's in the field
        field[end-string] = 0;
    }

    return 1;
}

字符串field显然应足够大,甚至可以和string一样大以确保安全。我们可以像这样解析它:

char *field = calloc(strlen(input_string)+1, sizeof(char));

if (string_get_field(input_string, delim_str, 0, field))
    v1 = strtod(field, NULL);

if (string_get_field(input_string, delim_str, 1, field))
    v2 = strtod(field, NULL);

free(field);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接