首页 > 解决方案 > 计算器练习中 fgets() 和 sscanf() 的问题

问题描述

我正在努力做我必须做的运动。因此我必须编写一个计算器。它只需要知道 int 和 +、-、/ 和 * 等运算符,我们需要使用 fgets() 和 sscanf()。

例如,如果我首先输入 5+6,它工作得很好。作为输出,我得到了正确的输出:5+6=11。如果我的第二个条目是“a”或“4”之类的东西,它不会像它应该做的那样打印出“无效输入”。相反,它会打印 4+6=10,因此加号和 6 会自动相加。我认为它与 fgets() 及其内存有关,但我不知道如何解决这个问题。到目前为止,这是我的代码:

#include <stdio.h>

int main(int argc, char *argv[])
{

   /* First define the variables, which are needed. 
There are two numbers, type int, and one op for the operation. */
   int number1, number2;
   char operator;
   char entry[50];

   /* First Output, where numbers and Operation are added, after that the code has to scan, which numbers and which operator were choosen.*/

   while (1)
   {
      printf("Enter <int> <op> <int>, single '0' to exit:");
      
      fgets(entry, sizeof(entry), stdin);
      sscanf(entry, "%d %c %d", &number1, &operator, & number2);
   
      /* Different calculations regarding the operator*/

      if (operator== '+')
      {
         printf("%d + %d = %d", number1, number2, number1 + number2);
      }

      else if (operator== '-')
      {
         printf("%d - %d = %d", number1, number2, number1 - number2);
      }

      else if (operator== '*')
      {
         printf("%d * %d = %d", number1, number2, number1 * number2);
      }

      else if (operator== '/')
      {
         printf("%d / %d = %d", number1, number2, number1 / number2);
      }
      else if (number1 == 0)
      {
         printf("Goodbye");
         break;
      }
      else if (number2=="\0" && operator =="\0" ||operator=="\0" )
      {
         printf("invalid input");
      }
      else
      {
         printf("invalid input");
      }
      printf("\n");
   }
   return 0;
}

谢谢你的帮助 :)

例子:

输入,单 '0' 退出:4+5 4 + 5 = 9

输入,单 '0' 退出:f 4 + 5 = 9

输入,单 '0' 退出:g 4 + 5 = 9

输入,单 '0' 退出:6-4 6 - 4 = 2

Enter , 单 '0' 退出:7 7 - 4 = 3

输入,单 '0' 退出:gergevyd 7 - 4 = 3

标签: cscanfcalculatorfgets

解决方案


正如@kaylum 指出的那样,您遇到的问题是,您没有验证转换结果。代码中的验证与实现正确的逻辑一样重要(如果由于验证失败而导致未定义的行为,则更是如此)

您必须验证每个用户输入和每个转换。您正在做正确的事情来接受输入fgets()。使用面向行的函数进行用户输入可以避免大量问题,因为您一次会使用整行输入。如果稍后转换失败,那么您将受到保护,不会出现任何stdin未读的错误字符。(您可以检查strlen()输入的 和'\n'存储在缓冲区中的内容的末尾以entry进行进一步验证)

但是,您仍然需要检查返回,因为用户通过按下(或在 Windows 上)fgets()取消输入是生成手动的有效输入。因此,只需检查 return from是否会表明,例如Ctrl + DCtrl + zEOFfgets()NULLEOF

    ...
    if (!fgets(entry, sizeof(entry), stdin)) {      /* validate EVERY input */
        puts ("(user canceled input)");             /* handle manual EOF case */
        break;
    }

使用sscanf(),您必须检查每个有效转换是否成功,并且您没有遇到匹配失败(如输入'a'整数)或在第一次有效转换之前到达的输入失败。返回发生的成功转换次数,因此只需计算您的转换说明符,然后确保返回等于该数字,例如EOFsscanf()

    /* validate EVERY conversion */
    if (sscanf (entry, "%d %c %d", &number1, &operator, & number2) != 3) {
        fputs ("error: invalid integer input.\n", stderr);
        continue;                                   /* go get new input */
    }
    ...

注意:通过使用continue;失败,您只会使程序丢弃当前行并提示用户输入新行)


额外的想法

关于您的登录,这是我的一些进一步想法(包括在下面的评论中)。当你需要一个常数时,#define一个(或多个)。这样,您可以在代码顶部有一个方便的位置,以便在需要时进行更改,并且您不必通过声明或循环限制进行挑选。使用sizeof时,正确的语法是sizeof objector sizeof (type)。仅当您请求 a 的大小时才需要括号type(但在其他情况下不是错误)

每当您使用单个数字或字符来确定要采用多个分支中的哪个分支时,请考虑使用switch()语句而不是if .. else if ... else if ... else ...语句链。方便很多。

在进行除法时——总是要防止"divide by zero"(否则会发生坏事——尽管生成的代码通常会将它们作为异常处理)。

使用 时fgets(),您可以提供一种非常简单的方法来指示输入结束。由于fgets()(以及 POSIX getline())读取并存储它们'\n'作为它们填充的缓冲区的一部分,您只需要检查第一个字符entry是否'\n'要退出(这是一种方便的方法)。通话后fgets(),您只需检查是否*entry == '\n'确定用户是否已完成。( *entry == *(entry + 0) == entry[0]) 指针的简单取消引用是检查第一个字符(或第一个元素)的快速方法。

考虑到这些,您可以考虑以下内容:

#include <stdio.h>

#define MAXC 1024       /* if you need a constant, #define one (or more) */

int main (void) {       /* if no arguments are expected */

    int number1, number2;
    char entry[MAXC], operator;
    
    while (1)   /* loop continually */
    {   /* no special input to exit, break read-loop on blank line */
        fputs ("\n('Enter' to exit)\nEnter expression: int op int: ", stdout);
        
        /* while not EOF or blank line */
        if (!fgets (entry, sizeof entry, stdin) || *entry == '\n') {
            puts ("(user canceled input)");
            break;
        }
        
        /* validate EVERY conversion */
        if (sscanf (entry, "%d %c %d", &number1, &operator, & number2) != 3) {
            fputs ("  error: invalid integer input.\n", stderr);
            continue;                                   /* go get new input */
        }
        
        /* Different calculations regarding the operator*/
        switch (operator)
        {               /* just add \n to end of each format string */
            case '+':   printf ("%d + %d = %d\n", number1, number2, number1 + number2);
                        break;
            case '-':   printf ("%d - %d = %d\n", number1, number2, number1 - number2);
                        break;
            case '*':   printf ("%d * %d = %d\n", number1, number2, number1 * number2);
                        break;
            case '/':
                        if (number2 == 0)   /* handle divide by zero */
                            fputs ("  error: division by 0 exception.\n", stderr);
                        else
                            printf ("%d / %d = %d\n", number1, number2, number1 / number2);
                        break;
            default:    fprintf (stderr, "  error: invalid operator '%c'.\n", operator);
                        break;
        }
    }
}

注意:entry可以容纳的字符数量的增加。不要吝啬缓冲区大小。除非你在微控制器上,否则 1k 缓冲区很好,可以处理除猫踩踏的最坏情况外的所有情况-键盘

示例使用/输出

$ ./bin/calculator

('Enter' to exit)
Enter expression: int op int: 1 + 1
1 + 1 = 2

('Enter' to exit)
Enter expression: int op int: 1 + z
  error: invalid integer input.

('Enter' to exit)
Enter expression: int op int: 4 | 8
  error: invalid operator '|'.

('Enter' to exit)
Enter expression: int op int: 10/1
10 / 1 = 10

('Enter' to exit)
Enter expression: int op int: 10/0
  error: division by 0 exception.

('Enter' to exit)
Enter expression: int op int: 25 - 13
25 - 13 = 12

('Enter' to exit)
Enter expression: int op int:
(user canceled input)

看看事情,如果你有问题,请告诉我。


推荐阅读