【问题标题】:check if input is a number and if not return to input检查输入是否为数字,如果不是返回输入
【发布时间】:2012-03-24 21:09:52
【问题描述】:

所以,我是这个网站的新手,我是编程新手。我在我的第一本书上,我爸爸给了一个“家庭作业”,他不知道做这个的代码,但我想做。所以这就是我想要做的: 我想创建一个像计算器一样的程序,在其中输入一个值 A 和一个值 B,然后程序显示 A + B 的结果。我已经做到了,问题是如果我输入一个字符,程序会继续读取其他代码。这是我写的,也许你会更好理解。

#include <stdio.h>
#include <conio.h>
#include <ctype.h>
void main(void)
  {
   int val_a;
   int val_b;
   int result;

   printf("Write a number A=");
   scanf("%d", &val_a);
   if(isdigit(val_a))
   {
    printf("\nWrite a number B=");
    scanf("%d", &val_b);
   }
else
  printf("\nI said number.\n");
result = val_a + val_b;
printf("%d + %d = %d", val_a, val_b, result);
getch();
printf("\033[2J");   \\ to clear the screen
}

所以我想要的是程序会回到我必须输入数字 A 的地方。 我在谷歌上找到了关于标签的信息,但我什么都不懂:D。也许有人可以发布我想要的示例,以便我可以研究它。

我应该写我的名字吗?也许下次:P

PS:我希望有人能在我写的内容中发现一个 NOOB 错误并告诉我。 :祈祷:

【问题讨论】:

  • 你知道do...while声明是什么吗?
  • 当您说“输入一个字符”时,您的意思是输入一个非数字字符吗?
  • @littleadv 我不知道“do...while”语句是什么。我认为这是在我的 C 书的下一课中。
  • @CareyGregory 你能更具体一点吗?我不明白你的意思。

标签: c input


【解决方案1】:

批评

#include <stdio.h>
#include <conio.h>

所以你在 Windows 上工作。

#include <ctype.h>
void main(void)

main() 的返回类型应该是int,尽管你会发现很多相反的例子。

{
    int val_a;
    int val_b;
    int result;

    printf("Write a number A=");
    scanf("%d", &val_a);

您应该测试来自scanf() 的返回值,看看它是否能够读取数字。

    if (isdigit(val_a))

此测试“有效”,但它会检查输入的数字是否为 48..57 范围内的值(这是 ASCII、CP1252、Unicode、ISO 8859-1 等数字 '0''9')。这可能不是你的想法。

    {
        printf("\nWrite a number B=");
        scanf("%d", &val_b);

用户输入了换行符,因此printf() 格式字符串前面的换行符不是必需的,尽管它也无害。关于检查scanf() 的相同评论也适用于此处。

    }
    else
        printf("\nI said number.\n");

最好做错误检查。但是,即使您检测到错误,您也会继续前进。你真的需要停下来。

    result = val_a + val_b;
    printf("%d + %d = %d", val_a, val_b, result);

总的来说,最好用换行符结束行。它将数据刷新到文件或屏幕。

    getch();
    printf("\033[2J");   \\ to clear the screen

printf() 格式字符串不可移植。鉴于您包含&lt;conio.h&gt;,您可能应该使用clrscr()(它可能会将该转义序列或等效序列发送到终端)。我不认为有必要在程序结束时清除屏幕,但我主要在 Unix 系统上工作,而不是在 Windows 上工作。

}

鉴于 MSVC 仍然是 C89 编译器,您应该在程序末尾包含 return 0; 以返回成功状态。

重写

添加所有更改,您最终会得到:

#include <stdio.h>
#include <conio.h>

int main(void)
{
    int val_a;
    int val_b;
    int result;

    printf("Write a number A=");
    if (scanf("%d", &val_a) != 1)
    {
        printf("I said number.\n");
        getch();
        clrscr();
        return(1);
    }
    printf("\nWrite a number B=");
    if (scanf("%d", &val_b) != 1)
    {
        printf("I said number.\n");
        getch();
        clrscr();
        return(1);
    }

    result = val_a + val_b;
    printf("%d + %d = %d\n", val_a, val_b, result);
    getch();
    clrscr();
    return(0);
}

当你学会了编写函数时,你可以把它压缩成:

#include <stdio.h>
#include <conio.h>

static int get_a_number(const char *prompt)
{
    int value;
    printf(prompt);
    if (scanf("%d", &value) != 1)
    {
        printf("I said number.\n");
        getch();
        clrscr();
        exit(1);
    }
    return value;
}

int main(void)
{
    int val_a  = get_a_number("Write a number A=");
    int val_b  = get_a_number("Write a number B=");
    int result = val_a + val_b;
    printf("%d + %d = %d\n", val_a, val_b, result);
    getch();
    clrscr();
    return(0);
}

get_a_number() 函数不是通用函数,但它在这种情况下很有用,因为它封装了一段通用代码。

  • 子例程调用允许我们总结参数列表中的不规则性
  • 子程序本身总结了代码的规律

这是 Kernighan & Plauger 的一句话,“编程风格的要素”。


使用atexit()

上面的代码已更新为在所有退出路径上包含getch()clrscr()(并删除了#include &lt;ctype.h&gt;,因为其中没有任何函数被使用)。重复写出两个函数调用很麻烦。避免该问题的另一种方法是编写一个在程序退出时调用的函数。您使用atexit() 函数注册它,该函数在&lt;stdlib.h&gt; 中声明:

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

static void wait_at_exit(void)
{
    getch();
    clrscr();
}

static int get_a_number(const char *prompt)
{
    int value;
    printf(prompt);
    if (scanf("%d", &value) != 1)
    {
        printf("I said number.\n");
        exit(1);
    }
    return value;
}

int main(void)
{
    atexit(wait_at_exit);
    int val_a  = get_a_number("Write a number A=");
    int val_b  = get_a_number("Write a number B=");
    int result = val_a + val_b;
    printf("%d + %d = %d\n", val_a, val_b, result);
    return(0);
}

main() 中的代码假定您可以任意混合语句(例如 atexit())和声明(例如 int val_a)。标准 C 的当前 (2011) 和旧 (1999) 版本都允许这样做,但标准 C 的原始 (1989) 版本不允许这样做。我相信 MSVC 遵守原始标准。如果是这样,您可能需要将 main() 写为:

int main(void)
{
    atexit(wait_at_exit);
    {
    int val_a  = get_a_number("Write a number A=");
    int val_b  = get_a_number("Write a number B=");
    int result = val_a + val_b;
    printf("%d + %d = %d\n", val_a, val_b, result);
    }
    return(0);
}

内部块(内部大括号)内的代码是否应该缩进一个额外的级别是一个很好的讨论点。您始终可以在用大括号括起来的代码块的开头声明变量,这样我就可以编写对atexit() 的调用,然后定义和使用这些变量。

如果您阅读有关敏捷编程的文章,您会发现其中的一个口头禅是:

  • 干——不要重复自己

上面的重写以避免重复的getscr() 调用可以被认为是该口头禅的应用。与get_a_number() 函数类似;它适用于 DRY 口头禅。


再试一次

如果你想让程序在用户输入错误时返回并再次提示,那么你需要修改get_a_number()函数。而且,由于函数变得相当复杂,您肯定需要使用函数来完成这项工作。您还遇到了scanf() 的问题;它不允许您每行强制执行一个数字。如果您在第一次提示时键入1 2,对get_a_number() 的第一次调用将读取一个和空白,停止解析(并将空白放回以供重用),并返回值 1。第二次调用to get_a_number() 将输出提示但返回值 2 而用户无需输入更多内容。如果这不是您想要的行为,那么您必须安排阅读整个第一行并扫描数字,然后丢弃其余部分。还有另一个令人担忧的原因。如果用户键入a 而不是1,则scanf() 将永远不会再读取任何内容;它将进入一个循环,发现a 作为数字无效,返回0 转换完成,您的代码可能会进入无限循环,提示输入并读取a。这类问题就是为什么很多人(尤其是我)避免使用scanf()

避免上述大多数问题的最简单方法是使用fgets()sscanf()

enum { MAX_LINELENGTH = 4096 };
enum { MAX_ATTEMPTS   = 10   };

static int get_a_number(const char *prompt)
{
    int value;
    char line[MAX_LINELENGTH];
    int count = 0;
    while (fputs(prompt, stdout) != EOF &&
           fgets(line, sizeof(line), stdin) != 0)
    {
        if (sscanf(line, "%d", &value) == 1)
            return value;
        if (count++ > MAX_ATTEMPTS)
        {
            printf("I give in; I don't understand what you're typing\n");
            exit(1);
        }
        printf("I said please enter a number.\n");
    }

    printf("Oops: I got EOF or an error; goodbye!\n");
    exit(1);
}

这涵盖了很多问题。通常,回显错误数据是个好主意。你可以这样做:

        printf("I didn't understand what you meant when you typed:\n%s\n"
               "I said please enter a number.\n", line);

这使用'相邻字符串连接',并向用户显示程序接收到的内容,这通常可以帮助用户了解错误所在。

我使用fputs(prompt, stdout)而不是printf(prompt)来输出提示。两者都可以使用(printf(prompt) != 0 将是测试),但fputs() 有一点优势,因为如果有人设法破坏您的程序并在提示中输入%printf() 将尝试处理它没有传递一个参数,而fputs() 根本不解释它的参数字符串。在这里,差异可以忽略不计;可以合理地争辩说我把事情复杂化了。但是程序的安全性也是你最终要学习的东西,诸如“不要使用printf(variable)”之类的技术可以帮助你的程序更有弹性。 (其实复杂就是解释,代码很简单。)

另一个详细的讨论点是'在fputs() 之后和fgets() 之前是否应该有一个fflush()?可以将额外的“条件”fflush(stdout) == 0 &amp;&amp; 添加到循环控制中,但实际上通常没有必要。只有当标准输入来自终端以外的东西并且标准输出来自文件时,它才会有所作为。所以,我忽略了它。

循环中的计数器可防止出现严重错误——程序退出而不是永远挂起,这让无法正确输入数据的用户感到沮丧。您是否曾经有过一个程序弹出一个对话框,您只能在该对话框中点击“确定”按钮(即使它不是确定),然后一遍又一遍地再次返回同一个框?我希望不是为了你。我有;当它发生时让我很沮丧。

您可以看到为什么在get_a_number() 中编写代码一次比两次更有利;这将是很多重复。

【讨论】:

  • 非常感谢。但是如果我写一个字符,程序就会关闭。我想让它告诉我我写了一个字符,然后带我回到我必须输入数字的地方。希望你明白我现在想要什么:P 我也尝试了一个标签,但它进入了一个无限循环,我只能用 ctrl + break 来停止它。
  • +1,写得好!要让它继续询问,请将“if (scanf”更改为“while (scanf”并删除“exit(1);”行。
  • 所以发现Jonathan给出的例子有问题。我运行程序并写了一个字符,程序关闭,当我再次打开它时,程序首先告诉我我说了一个数字,然后提示我输入数字 A,即使我输入了(希望这是正确的过去形式 - - 我的母语是罗马尼亚语)数字 B 的一个字符。我不确定你明白我的意思。
  • @New_Gyku:要让程序在出现问题时也等待用户输入,您必须安排调用getc() 函数(也可能调用clrscr() 函数),即使有问题。此外,消失的 Windows 主要是 IDE 环境中的一个问题。我倾向于将“xterm”作为我的窗口,并在其中进行所有开发,而不是在 IDE 中。 (不使用 IDE 的一个原因是我使用的程序在 IDE 运行时表现不佳。)我将更新程序以(我希望)改善错误行为。
  • @JonathanLeffler:我看到你编辑了你的帖子。我什么也不懂
【解决方案2】:

您需要检查scanf() 是否成功读取了它应该读取的输入。通常,您应该在阅读后始终检查读取是否成功。 scanf() 返回成功读取输入的数量,这些输入基本上映射到它可以成功读取的格式说明符的数量。例如,您应该检查一下

if (scanf("%d, &val_a) == 1)) {
    ...
}

读取的值可能是也可能不是“数字”。一般来说,根据isdigit(),它可能不是数字,因为此函数仅对字符'0''1'、...、'9' 的整数值返回true。测试scanf() 是否可以读取其所有格式说明符应该可以避免这个问题。

如果scanf() 失败,您可以读取一个字符(使用例如fgetc())然后重试或读取整行(不是使用gets(),但使用fgets())。您可能还想打印一条输入不是预期的消息。

【讨论】:

    【解决方案3】:

    你把isdigit 函数弄错了。它旨在接收 char 参数,但在您的情况下, int 被转换,您可能会说降级。

    该函数基于字母的ASCII码link here。你可以试试这个自己看看。

    char c = '1';
    if(isdigit(c))
       printf("digit");
    

    此外,您需要的是do{ /* something */ } while(boolean);

    【讨论】:

      【解决方案4】:

      您误解了 isdigit 函数的使用。它确定 字符 是否为数字,例如“0”、“1”、“2”等。但是,您已经使用 scanf 函数将输入转换为整数。因此,只需删除对 isdigit 的调用,而是检查 scanf 的返回码,它会告诉您用户输入的数字是否有效。

      【讨论】:

        猜你喜欢
        • 2014-02-06
        • 1970-01-01
        • 1970-01-01
        • 2021-09-14
        • 2013-07-25
        • 2023-03-27
        • 1970-01-01
        • 2016-05-19
        相关资源
        最近更新 更多