在不使用 C 中的 scanf() 函数的情况下将十六进制输入存储到 int 变量中

Store hex input into int variable without using scanf() function in C

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

我在互联网上搜索了这个问题,发现如果在 getchar() 函数之前将 scanf() 函数实现到程序中,则 getchar() 函数不会以正确的方式运行, 并且会表现得像我的问题。

引用:

I will bet you ONE HUNDRED DOLLARS you only see this problem when the call to getchar() is preceded by a scanf().

Don't use scanf for interactive programs. There are two main reasons for this:

1) scanf can't recover from malformed input. You have to get the format string right, every time, or else it just throws away whatever input it couldn't match and returns a value indicating failure. This might be fine if you're parsing a fixed-format file when poor formatting is unrecoverable anyway, but it's the exact opposite of what you want to do with user input. Use fgets() and sscanf(), fgets() and strtok(), or write your own user input routines using getchar() and putchar().

1.5) Even properly used, scanf inevitably discards input (whitespace) that can sometimes be important.

2) scanf has a nasty habit of leaving newlines in the input stream. This is fine if you never use anything but scanf, since scanf will usually skip over any whitespace characters in its eagerness to find whatever it's expecting next. But if you mix scanf with fgets/getchar, it quickly becomes a total mess trying to figure out what might or might not be left hanging out in the input stream. Especially if you do any looping -- it's quite common for the input stream to be different on the first iteration, which results in a potentially weird bug and even weirder attempts to fix it.

tl;dr -- scanf is for formatted input. User input is not formatted. //

这里是 link,给那个线程:https://bbs.archlinux.org/viewtopic.php?id=161294


scanf() 与:

scanf("%x",integer_variable); 

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

是否有不同的方法从标准输入输入十六进制值并将其存储到整数变量中?

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


信息:我在 Whosebug 上阅读了很多关于 C 的线程,但其中 none 很好地回答了我的明确问题。所以我发布了这个问题。

我在 Linux Ubuntu.

手下工作

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

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

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

如您发布的 link 中所述,使用 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);
}    

关于百元赌注的报价是准确的。将 scanfgetchar 混合几乎总是一个坏主意;它几乎总是会带来麻烦。不过,并不是说它们 不能 一起使用。可以一起使用它们——但通常,这太难了。有太多挑剔的小细节和 "gotcha!" 需要跟踪。麻烦多过得不偿失。

一开始你说过

scanf() with ... %d ... seems for me as a newbie to the scene as the only way possible to input a hex number from the keyboard

那里有些混乱,因为 %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);

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

有时做这种事情是有用的或必要的"by hand"。下面是一个代码片段,它正好读取两个十六进制数字。我将从使用 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 家族的成员,但这是我有时使用的一个地方,正是因为这样做 "by hand" 工作量很大。您可以通过使用辅助函数或宏来进行数字转换来大大简化它:

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. 代码到达文件末尾;根本没有可读的字符。

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

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

乙。如果您再次调用 fgets,请始终检查 return 值。您将在 EOF(问题 5)上得到 NULL。处理其他问题取决于您对所读行的处理。

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

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

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