【问题标题】:Store hex input into int variable without using scanf() function in C将十六进制输入存储到 int 变量中而不使用 C 中的 scanf() 函数
【发布时间】:2026-01-18 00:45:01
【问题描述】:

史前: 我遇到了问题,getchar() 函数没有以正确的方式得到处理,因为没有对任何给定输入的请求,程序只是继续进一步处理。

我在互联网上搜索了这个问题可能是什么,并发现如果 scanf() 函数在 getchar() 函数之前实现到程序中,getchar() 函数的行为方式不正确,并且会像我的问题一样。

引用:

我敢打赌你只有在调用 getchar() 之前有一个 scanf() 时才会看到这个问题。

不要将 scanf 用于交互式程序。这主要有两个原因:

1) scanf 无法从格式错误的输入中恢复。您必须每次都正确获取格式字符串,否则它只会丢弃任何无法匹配的输入并返回一个指示失败的值。如果您在解析固定格式文件时无论如何都无法恢复不良格式,这可能会很好,但这与您想要对用户输入执行的操作完全相反。使用 fgets() 和 sscanf()、fgets() 和 strtok(),或者使用 getchar() 和 putchar() 编写自己的用户输入例程。

1.5) 即使使用得当,scanf 也不可避免地会丢弃有时可能很重要的输入(空白)。

2) scanf 有个讨厌的习惯,就是在输入流中留下换行符。如果您只使用 scanf 之外的任何东西,这很好,因为 scanf 通常会跳过任何空白字符,因为它急于找到接下来要查找的任何内容。但是,如果您将 scanf 与 fgets/getchar 混合使用,那么很快就会变得一团糟,试图弄清楚输入流中可能会或可能不会留下什么。尤其是如果您进行任何循环 - 输入流在第一次迭代时不同是很常见的,这会导致潜在的奇怪错误,甚至会尝试更奇怪的修复它。

tl;dr -- scanf 用于格式化输入。用户输入未格式化。 //

这是该主题的链接:https://bbs.archlinux.org/viewtopic.php?id=161294


scanf() 与:

scanf("%x",integer_variable); 

对于我来说,作为一个新手,我似乎是从键盘输入十六进制数字(或者更好地说是标准输入文件)并将其存储到 int 变量的唯一方法。

有没有其他方法可以从标准输入输入十六进制值并将其存储到整数变量中?

额外挑战:如果我可以将负值(当然通过负十六进制输入)写入有符号的 int 变量,那也很好。


INFO:我在 * 上阅读了很多关于 C 的主题,关于类似的问题,但没有一个很好地回答了我的明确问题。所以我发布了这个问题。

我在 Linux Ubuntu 下工作。

【问题讨论】:

    标签: c integer hex scanf


    【解决方案1】:

    关于百元赌注的报价是准确的。将scanfgetchar 混合几乎总是一个坏主意。它几乎总是会导致麻烦。但这并不是说它们不能一起使用。可以一起使用它们——但通常,这太难了。有太多繁琐的小细节和“陷阱!”需要跟踪。麻烦多于其价值。

    一开始你说过

    scanf() with ... %d ... 对我来说,这似乎是从键盘输入十六进制数字的唯一方法

    那里有些混乱,因为%d当然是用于十进制输入。但是,由于在您纠正该问题时我已经写了这个答案,所以我们现在继续使用小数。 (目前我也忽略了错误检查——也就是说,如果用户没有输入请求的数字,这些代码片段不会检查或做任何优雅的事情。)无论如何,这里有几种阅读方法整数:

    1. scanf("%d", &integer_variable);
      没错,这是(表面上)最简单的方法。

    2. char buf[100];
      fgets(buf, sizeof(buf), stdin);
      integer_variable = atoi(buf);
      我认为这是不使用scanf 的最简单方法。但是现在大多数人都不喜欢使用atoi,因为它没有做太多有用的错误检查。

    3. char buf[100];
      fgets(buf, sizeof(buf), stdin);
      integer_variable = strtol(buf, NULL, 10);
      这与以前几乎相同,但避免使用atoi,而使用首选的strtol

    4. char buf[100];
      fgets(buf, sizeof(buf), stdin);
      sscanf(buf, "%d", &integer_variable);
      这会读取一行,然后使用 sscanf 对其进行解析,这是另一种流行且通用的技术。

    所有这些都会起作用;所有这些都将处理负数。不过,考虑错误条件很重要——我稍后会对此进行更多说明。

    如果要输入十六进制数,技巧类似:

    1. scanf("%x", &integer_variable);

    2. char buf[100];
      fgets(buf, sizeof(buf), stdin);
      integer_variable = strtol(buf, NULL, 16);

    3. char buf[100];
      fgets(buf, sizeof(buf), stdin);
      sscanf(buf, "%x", &integer_variable);

    这些也都应该有效。不过,我不一定期望他们处理“负十六进制”,因为这是一个不寻常的要求。大多数时候,无符号整数使用十六进制表示法。 (实际上,严格来说,%xscanfsscanf 必须与已声明为unsigned intinteger_variable 一起使用,而不是普通的int。)

    有时“手工”做这类事情是有用或必要的。这是一个读取两个十六进制数字的代码片段。我将从使用getchar 的版本开始:

    int c1 = getchar();
    if(c1 != EOF && isascii(c1) && isxdigit(c1)) {
        int c2 = getchar();
        if(c2 != EOF && isascii(c2) && isxdigit(c2)) {
            if(isdigit(c1)) integer_variable = c1 - '0';
            else if(isupper(c1)) integer_variable = 10 + c1 - 'A';
            else if(islower(c1)) integer_variable = 10 + c1 - 'a';
    
            integer_variable = integer_variable * 16;
    
            if(isdigit(c2)) integer_variable += c2 - '0';
            else if(isupper(c2)) integer_variable += 10 + c2 - 'A';
            else if(islower(c2)) integer_variable += 10 + c1 - 'a';
        }
    }
    

    如您所见,这有点令人震惊。我,虽然我几乎从不使用scanf 家族的成员,但这是我有时会做的一个地方,正是因为“手工”做的工作量很大。您可以通过使用辅助函数或宏进行数字转换来大大简化它:

    int c1 = getchar();
    if(c1 != EOF && isascii(c1) && isxdigit(c1)) {
        int c2 = getchar();
        if(c2 != EOF && isascii(c2) && isxdigit(c2)) {
            integer_variable = Xctod(c1);
            integer_variable = integer_variable * 16;
            integer_variable += Xctod(c2);
        }
    }
    

    或者您可以将这些内部表达式折叠成只是

            integer_variable = 16 * Xctod(c1) + Xctod(c2);
    

    这些工作在辅助功能方面:

    int Xctod(int c)
    {
        if(!isascii(c)) return 0;
        else if(isdigit(c)) return c - '0';
        else if(isupper(c)) return 10 + c - 'A';
        else if(islower(c)) return 10 + c - 'a';
        else return 0;
    }
    

    或者也许是一个宏(尽管这绝对是一种老派的东西):

    #define Xctod(c) (isdigit(c) ? (c) - '0' : (c) - (isupper(c) ? 'A' : 'a') + 10)
    

    我通常不是使用getchar() 解析来自stdin 的十六进制数字,而是来自一个字符串。我经常使用字符指针 (char *p) 来遍历字符串,这意味着我最终得到的代码更像这样:

    char c1 = *p++;
    if(isascii(c1) && isxdigit(c1)) {
        char c2 = *p++;
        if(isascii(c2) && isxdigit(c2))
            integer_variable = 16 * Xctod(c1) + Xctod(c2);
    }
    

    忽略临时变量和错误检查并将其进一步简化是很诱人的:

    integer_variable = 16 * Xctod(*p++) + Xctod(*p++);
    

    但不要这样做!除了缺少错误检查之外,这个表达式可能是undefined,而且它肯定不会总是按照你的意愿行事,因为除了你读取字符的顺序之外,不再有任何保证。如果你 知道 p 指向两个十六进制数字中的第一个,你不想再折叠它了

    integer_variable = Xctod(*p++);
    integer_variable = 16 * integer_variable + Xctod(*p++);
    

    即使那样,这也仅适用于 Xctod 的函数版本,而不是宏,因为宏会多次评估其参数。


    最后,我们来谈谈错误处理。有很多可能性需要担心:

    1. 用户在没有输入任何内容的情况下点击 Return。
    2. 用户在数字之前或之后键入空格。
    3. 用户在数字后面输入了额外的垃圾。
    4. 用户键入非数字输入而不是数字。
    5. 代码到达文件尾;根本没有字符可供阅读。

    然后您如何处理这些取决于您使用的输入技术。以下是基本规则:

    A.如果您正在调用scanffscanfsscanf总是检查返回值。如果它不是 1(或者,在您有多个 % 说明符的情况下,它不是您希望读取的值的数量),这意味着出现了问题。这通常会捕获问题 4 和 5,并且会优雅地处理案例 2。但它通常会悄悄地忽略问题 1 和 3。(特别是,scanffscanf 将额外的 \n 视为前导空格。)

    B.如果您再次调用fgets,请始终检查返回值。您将在 EOF 上获得 NULL(问题 5)。处理其他问题取决于您对所阅读的行做了什么。

    C.如果你调用atoi,它会优雅地处理问题2,但它会忽略问题3,它会悄悄地将问题4变成数字0(这就是为什么atoi通常不再推荐的原因)。

    D.如果您正在调用strtol 或任何其他“strto”函数,它们将优雅地处理问题 2,如果您让它们返回“结束指针”,您可以检查并处理问题 3 和4.(请注意,我在上面的两个strtol 示例中省略了结束指针处理。)

    E.最后,如果你正在做一些像我的“hardway”两位数十六进制转换器这样的肮脏的事情,你通常必须明确地自己解决所有这些问题。如果你想跳过前导空格,你必须这样做(<ctype.h> 中的isspace 函数可以提供帮助),如果可能有意外的非数字字符,你也必须检查这些字符。 (这就是对isasciiisxdigit 的调用在我的“hardway”两位数十六进制转换器中所做的事情。)

    【讨论】:

    • "@Steve Summit" 总是这些侧面的混乱。我又写错了代码。谢谢史蒂夫,我已经在我的问题中编辑了它。
    • 这是如此多的精彩信息,非常感谢。我不打算用那个来毁掉一个人的一天时间:-)
    • @RobertS 谢谢,欢迎接受答案,但是:“毁了”?一点都不。如果我真的不喜欢这样做,你认为我会花时间写这样的答案吗?
    • @xOneca 这是为了解决角色可能是负面的可能性,但我忘记了这可能不是正确的方法。
    【解决方案2】:

    根据scanf 手册页,您可以使用 scanf 将十六进制数从标准输入读取到(无符号)整数变量中。

    unsigned int v ;
    if ( scanf("%x", &v) == 1 ) {
       // do something with v.
    }
    

    根据手册页,%x 始终未签名。如果要支持负值,则必须添加显式逻辑。

    【讨论】:

    • @RobertS 您可能想澄清这个问题。我在回答“是否有其他方法可以从标准输入输入十六进制值并将其存储到整数变量中?” +“奖金”。
    • 对不起,我在编写代码时犯了一个错误,我在 scanf 命令中将 "%x" 与 "%d" 搞砸了。现在已编辑。
    【解决方案3】:

    正如您发布的链接中所述,使用fgetssscanf 是处理此问题的最佳方法。 fgets 将读取整行文本,sscanf 将解析该行。

    例如

    char line[100];
    fgets(line, sizeof(line), stdin);
    
    int x;
    int rval = sscanf(line, "%x", &x);
    if (rval == 1) {
        printf("read value %x\n", x);
    } else {
        printf("please enter a hexadecimal integer\n");
    }
    

    由于您只读取单个整数,因此您也可以使用strtol 而不是sscanf。这还具有检测是否输入了任何其他字符的优点:

    char *ptr;
    errno = 0;
    long x = strtol(line, &ptr, 16);
    if (errno) {
        perror("parsing failed");
    } else if (*ptr != '\n' && *ptr != 0) {
        printf("extra characters entered: %s\n", ptr);
    } else {
        printf("read value %lx\n", x);
    }    
    

    【讨论】:

    • 我喜欢区分尾随空格和其他额外字符,尽管这样区分无疑更令人讨厌。 (如果strtol 也处理尾随空格,那可能会很好。)