在简单的等式中用 scanf 替换 gets 会使程序崩溃

Replacing gets with scanf in simple equation crashes the program

我正在学习 C for Dummies。它有一个使用 getsatoi.

将英寸转换为厘米的示例代码(第 135 页,如果您有文本)

现在,我想尝试使用 scanf 而不是可怕的 gets,这是我能想到的最好的方法(基于作者提供的代码)。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    float height_in_cm;
    char height_in_inches;

    printf("Enter your height in inches: ");
    scanf("%c"),&height_in_inches;
    height_in_cm = atoi(height_in_inches)*2.54;
    printf("You are %.2f centimetres tall.\n",height_in_cm);
    return(0);
}

程序启动,但输入后就崩溃了。我哪里错了?

您在转换时遇到问题。

atoi 接受一个 const char* 作为输入。

您正在传递一个 char,因此它隐式地将 char 转换为一个点,糟糕的事情。

根据 user3121023 的建议,将 height_in_inches 更改为字符串。

char height_in_inches[20];

并使用 %s 阅读

scanf("%s", height_in_inches);

既然 scanf() 可以为您完成,为什么还要花时间转换答案?

#include <stdio.h>

int main(void)
{
    float height_in_inches;

    printf("Enter your height in inches: ");
    if (scanf("%f", &height_in_inches) == 1)
    {
        float height_in_cm = height_in_inches * 2.54;
        printf("You are %.2f inches or %.2f centimetres tall.\n",
               height_in_inches, height_in_cm);
    }
    else
        printf("I didn't understand what you said\n");
    return(0);
}

如果必须读取字符串,则使用fgets():

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char line[4096];

    printf("Enter your height in inches: ");
    if (fgets(line, sizeof(line), stdin) != 0)
    {
        double height_in = strtod(line, 0);
        double height_cm = height_in * 2.54;
        printf("You are %.2f inches or %.2f centimetres tall.\n",
               height_in, height_cm);
    }
    return(0);
}

请注意,两个程序都会在使用输入结果之前检查输入是否发生。您可以争辩说 strtod() 调用的错误检查非常乏味;我同意。请注意,我在第一个片段的 float 和第二个片段的 double 之间切换;两者都可以工作。当结果是分数时,我认为没有特别的理由将输入限制为整数值。回显输入和输出通常也是有益的;如果您得到的回显不是您认为输入的内容,则这是一个很好的提示,表明出现了严重错误并让您在代码的正确区域进行搜索。

注意:地毯下有许多小细节,特别是第一个关于 floatdouble 和 [=13 的例子=] 和 printf()。鉴于下面的评论,它们目前与 OP 无关。

对原始代码的最少修复

由于上面的代码比 OP 识别的更复杂,这里是对原始代码的一组更简单的修复。将字符串输入到数组(string)中是关键点;这也需要在 scanf() 调用中进行更改。 通过使用大缓冲区,我们可以假设用户将无法通过在终端上键入来溢出输入。不过,对于机器驱动的输入来说,这是不行的。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    float height_in_cm;
    char height_in_inches[4096];    // Array, big enough to avoid most overflows

    printf("Enter your height in inches: ");
    // Missing error check on scanf() — too advanced as yet
    scanf("%s", height_in_inches);  // Format specifier, parentheses, ampersand
    height_in_cm = atoi(height_in_inches) * 2.54;
    printf("You are %s inches or %.2f centimetres tall.\n",
           height_in_inches, height_in_cm);
    return(0);
}

一行输入有多长?

user3629249 :

a LOT of stack space can be saved by:

  1. noticing that an 'int' is only a max of 12 characters so the max length on the input buffer is 13 characters (to allow for the NUL string termination byte)
  2. limit the scanf() to 12 characters. I.E. 'scanf( "%12s", myCharArray );
  3. in C, the name of an array degrades to the address of the array, so not leading '&' needed on 'myCharArray'.

第3点正确;如果您使用 char myCharArray[13];,则在调用 scanf() 等时不会使用 &myCharArray;你只使用 myCharArray。如果您滥用 &.

,一个好的编译器会指出您的方法的错误

不过,我对第 1 点和第 2 点有疑问。 很多 的麻烦可以避免,注意如果用户在线上键入 9876432109876543210,那么使用 scanf()%12s 不会有太大帮助消除无效输入。它会在行中留下 8 个未读取的数字,而读取的内容仍然会溢出一个 32 位整数。如果您在较长的字符串上使用 strtoX() 系列函数而不是 atoi(),那么它们会检测到诸如溢出之类的问题,scanf()%datoi() 都不是做。 (这是主要答案中掩盖的许多要点之一。)

此外,在具有兆字节(通常是千兆字节)主内存的系统上,堆栈上的 4 KiB 不是主要问题。也就是说,我使用 4 KiB 部分是因为它的冲击值;但 POSIX 要求 [LINE_MAX] 的最小值为 2048。

如果您正在阅读基于行的输入(这通常是在命令行应用程序中进行输入的一种好方法),那么您要确保阅读整行,因为在部分行中胡思乱想会造成混乱并且很难报告错误。 fgets() 加上 sscanf() 处理的一个主要优点是您有一个完整的行,您可以在错误报告中使用它,而不是 scanf() 处理部分行后剩下的内容。如果第一次尝试失败,您也可以尝试以不同的方式扫描字符串;您不能使用 scanf() 系列中的直接文件 I/O 函数来做到这一点。

如果您天真地没有意识到人们键入的是长行,而您希望他们键入的是短行,那么您可以将剩余的行作为新行提供——无需进一步的用户交互——而实际上它是前一行的渣滓输入行。例如,如果您扫描 20 位中的 12 位,那么下一次输入将获得剩余的 8 位,而无需等待用户键入任何新内容,即使您提示他们输入更多。 (另外,请注意 using fflush(stdin);它是否能做任何有用的事情充其量是系统特定的。)

我使用了 fgets() 和 4 KiB 缓冲区。如果你想防止程序在一行中发送数兆字节的 JSON 编码数据,你需要使用 POSIX 的 getline() 函数来读取该行。它为整行分配了足够的 space ,除非内存不足。对于大多数学生练习作业,4 KiB 缓冲区和 fgets() 是一个合理的替代品。只要指数至少为 8,我愿意就使用的 2 的幂进行协商 — 值至少为 256。例如,将行缓冲区限制为 80 个字符不会阻止用户输入超过一行 80 个字符。这只是意味着额外的字符不太可能得到适当的处理。将缓冲区限制为 13 个字符不会给你带来任何有价值的东西,IMO,'saving stack space' 是一个过早的优化。