如何测试C语言中输入的字符串是否符合正确的“格式”?

7

我已经用C语言编写了一个简单的计算器应用程序,目前它运行得相当不错,但是我缺少一件事情:如何确保用户输入的格式正确,例如"6 + 7"。有没有办法进行测试?

以下是我的代码:

/*A simple calculator that can add, subtract and multiple. TODO: Division
and add error handling i.e. if not an expected input!*/

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

int main() {
  char input[10];
  int result;
  printf("Welcome to the calculator! The available operations are +, - and *.\n");
  while (1) {
    printf("What would you like to calculate?\n");
    fgets(input, 10, stdin); /*Getting user input in form e.g. 4 + 7*/

    /*Ideally test for input should go here*/

    /*Separating user input into the numbers and operators, using strtok and
    space as a delimiter*/
    char *firstNumber = strtok(input, " ");
    char *operator = strtok(NULL, " ");
    char *secondNumber = strtok(NULL, " ");

    /*Converting the separated numbers to integers*/
    int firstInt = atoi(firstNumber);
    int secondInt = atoi(secondNumber);

    if (strcmp(operator, "+") == 0) {
      result = firstInt + secondInt;
    } else if (strcmp(operator, "-") == 0) {
      result = firstInt - secondInt;
    } else if (strcmp(operator, "*") == 0) {
      result = firstInt * secondInt;
    } else {
      printf("That ain't a valid operator sonny jim. Try again:\n");
      continue;
    }

    printf("Your result is %d.\n", result);

    int flag = 0;

    while (flag == 0) {
      printf("Would you like to do another calculation? (yes or no)\n");
      fgets(input, 10, stdin);
      if (strcmp(input, "yes\n") == 0) {
        flag = 1;
      } else if (strcmp(input, "no\n") == 0) {
        flag = 2;
      } else {
        printf("That isn't a valid response. Please respond yes or no.\n");
      }
    }
    if (flag == 2) {
      break;
    }
  }

  return 0;

}

你可以使用类似 "%d %c %d"sscanf 函数,并检查其返回值。如果返回值为 3,则表示成功。然后,您只需要检查运算符是否有效即可。 - Eraklon
在决定使用scanf之前,您可能想阅读一下这篇文章:一个远离scanf()的初学者指南 - Andreas Wenzel
如果您想要评估嵌套表达式,您需要一个适当的语法和相应的解析器。 - EOF
3个回答

3

不一定是最好的方法,但我会这样使用sscanf

    int firstInt;
    int secondInt;
    char operator;
    if (3 != sscanf(input, "%d %c %d %1[^\n]", &firstInt, &operator, &secondInt, &temp)) {
      printf("Invalid input Billy!\n");
      continue;
    }

sscanf应该在成功从输入字符串中读取值时返回3。如果返回4,则表示它读取了一些无效的后置非空格字符。这种方法的额外好处是您不需要在其他地方使用atoi来解析操作数。

整个代码:

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

int main() {
  char input[10];
  char temp[2];
  int result;
  printf("Welcome to the calculator! The available operations are +, - and *.\n");
  while (1) {
    printf("What would you like to calculate?\n");
    fgets(input, 10, stdin); /*Getting user input in form e.g. 4 + 7*/

    /*Ideally test for input should go here*/
    int firstInt;
    int secondInt;
    char operator;
    if (3 != sscanf(input, "%d %c %d %1[^\n]", &firstInt, &operator, &secondInt, temp)) {
      printf("Invalid input Billy!\n");
      continue;
    }

    if ('+'== operator) {
      result = firstInt + secondInt;
    } else if ('-' == operator) {
      result = firstInt - secondInt;
    } else if ('*' == operator) {
      result = firstInt * secondInt;
    } else {
      printf("That ain't a valid operator sonny jim. Try again:\n");
      continue;
    }

    printf("Your result is %d.\n", result);

    int flag = 0;

    while (flag == 0) {
      printf("Would you like to do another calculation? (yes or no)\n");
      fgets(input, 10, stdin);
      if (strcmp(input, "yes\n") == 0) {
        flag = 1;
      } else if (strcmp(input, "no\n") == 0) {
        flag = 2;
      } else {
        printf("That isn't a valid response. Please respond yes or no.\n");
      }
    }
    if (flag == 2) {
      break;
    }
  }
  return 0;
}

为什么有些条件被倒置了,而其他条件则按正确顺序排列?我建议保持一致性:要么全部倒置,要么全部按正确顺序排列。 - pmg
2
在 scanf 格式字符串的末尾放置换行符是毫无意义且令人困惑的。如果你想检查是否已经扫描了整个字符串,请使用 %n -- sscanf(input, "%d %c%d %n", &first, &op, &second, &len) >= 3 && input[len] == 0 - Chris Dodd
1
char temp; ... sscanf(input, "%*d %*c %*d%1[^\n]", &temp) 代码会导致 undefined behavior (UB),因为 temp 太小了。虽然这样做不太好,但很容易解决。目前还不清楚为什么第一个 sscanf() 没有包含 1[^\n],并且允许返回值为3或4。 - chux - Reinstate Monica
1
我后来添加了第二个sscanf。然后我意识到它们可以合并成一个,但我还是保留了原样。但是由于我必须修复你发现的UB,所以我将它们合并了。谢谢! - Eraklon

2

如何确定C语言中的字符串格式是否正确?

一种更简单的方法是在末尾使用" %n",记录扫描的偏移量——如果它成功扫描到该位置。这与@Chris Dodd的评论类似。

int firstNumber;
char operator[2];
int secondNumber;

int n = 0;
sscanf(input, "%d %1[+-] %d %n", &firstNumber, operator, &secondNumber, &n);
//  v---v--------------------  Scan complete?
if (n > 0 && input[n] == '\0') Success();
//           ^--------------^  No extra junk at the end?
else Fail();

检测空格是否存在很棘手。这个答案和"%d %c %d"会通过`"5-7"`。如果需要在运算符周围添加空格,请使用
"%d%*[ ]%1[+-]*[ ]%d %n"

注意在 " %n" 中的 " " 允许扫描器容忍末尾的 '\n'。根据需要使用。

2
你应该首先检查输入的长度。你只允许输入10个字符。其中一个是运算符,两个是空格,一个是\n,还有一个是NUL终止符。这只留下5个字符分配给两个操作数。所以,如果用户输入543 * 65,你已经截断了\n。如果输入5432 * 65,你将开始丢失重要数据。我实现长度检查的方法是搜索\n
if (input[0] && input[strlen(input) - 1] != '\n'),你就知道输入已被截断。 接下来,你需要检查字符的有效性。如果你使用首选函数strtol()††将字符串转换为整数时保留你的strtok()方法,你可以在进行输入检查。至于运算符,你已经对其进行了检查。对于输入格式:检查NULL指针。如果strtok()找不到空格分隔符,它会返回一个NULL指针,然后你会在程序中尝试从这个指针读取。

†: 我个人会将输入字符限制增大:至少25个
††: 更多信息请参见man strtol


谢谢你发现了我的第二个错误 :) 已更正。 - Hello World
一个好的编译器会识别到 strlen(input) 两次使用而不改变 input,因此不会对 input 进行两次遍历。然而,一个较差的编译器会对 input 进行两次遍历。一个简单的替代方法,非常类似于 C 语言:if (input[0] && input[strlen(input) - 1] != '\n') - chux - Reinstate Monica
我不知道任何现代编译器不会完全优化掉它。个人而言,我更喜欢检查 strlen() 两次的可读性,而不是可能的优化,但我已经再次编辑了。 - Hello World

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接