检查第一个参数是否是仅由十进制数字组成的有效数字

Check if first argument is a valid number consisting of only decimal digits

这是我尝试用于“验证密钥”部分的代码,以便仅当参数为数字时才尝试 return 成功。但是,当我 运行 我的程序时,一些数字被认为是错误的。我不明白为什么。

~/pset2/caesar/ $ ./caesar 9
Usage: ./caesar key
~/pset2/caesar/ $ ./caesar 45
Success
45

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

int main(int argc, string argv[]) {
  int n = atoi(argv[1]);

  if (argc == 2 && isdigit(argv[1][n])) {
    printf("Success\n%s\n", argv[1]);
    return 0;
  } else {
    printf("Usage: ./caesar key\n");
    return 1;
  }

你的错误在这里:

if (argc == 2 && isdigit(argv[1][n])) {

这一行没有意义,您正在检查第一个参数的 n-th 字符是否为数字,但是 (1) 您甚至不知道第一个参数是否有足够的字符 (natoi 编辑 return,因此它可以任意大)和 (2) 您没有检查参数的 所有数字


如果您想检查提供给程序的第一个参数中的每个字符是否都是数字,您可以通过两种方式进行:

  1. 遍历每个字符并检查 isdigit():

    #include <stdio.h>
    #include <ctype.h>
    
    int main(int argc, char **argv) {
        char *c;
    
        if (argc != 2) {
            fputs("Usage: ./prog <key>\n", stderr);
            return 1;
        }
    
        for (c = argv[1]; *c != '[=11=]'; c++) {
            if (!isdigit(*c)) {
                fputs("Usage: ./prog <key>\n", stderr);
                return 1;
            }
        }
    
        printf("Success!\n%s\n", argv[1]);
        return 0;
    }
    
  2. 使用像 strtol 这样的函数( 而不是 atoi,因为它确实不是信号错误)。使用 strtol 还可以自动检查数字是否在可以存储在 long 中的值范围内(如果不在,LONG_MINLONG_MAX是 returned 并且 errno 已适当设置)。

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <limits.h>
    
    int main(int argc, char **argv) {
        long num;
        char *endp;
    
        if (argc != 2) {
            fputs("Usage: ./prog <key>\n", stderr);
            return 1;
        }
    
        errno = 0;
        num = strtol(argv[1], &endp, 10);
    
        if (endp == argv[1] || *endp != '[=12=]' || errno == ERANGE) {
            fputs("Usage: ./prog <key>\n", stderr);
            return 1;
        }
    
        printf("Success!\n%ld\n", num);
        return 0;
    }
    

选项 2 的优点是已经为您转换了数字,因此我建议您选择选项 1。注意 strtol 可以 return如果给定的字符串以 - 开头(例如 -123),则为负值:如果您不允许使用负数,则可能需要检查一下。

when I run my program, some numbers are considered wrong.

使用像 "123" 这样的输入参数,下面的代码会尝试检查 3 个字符参数的第 123 个字符是否为数字。在 "123" 之外访问 argv[1] 未定义行为 (UB) - 错误。

int n = atoi(argv[1]);
if (... isdigit(argv[1][n])) ...  // UB

测试输入是否全是数字:

  1. 使用 argv[1].

    在 之前检查参数的预期数量
  2. 测试字符串的每个字符argv[1][].

样本

int main(int argc, string argv[]) {
  if (argc == 2) { 
    const char *digit = argv[1];
    while (*digit >= '0' && *digit <= '9') {
      digit++;
    }
    // If code reached the end of the string?
    if (*digit == '[=11=]') {
      printf("Success\n%s\n", argv[1]);
      return 0;
    }
  }
  printf("Usage: ./caesar key\n");
  return 1;
}

代码也可以使用 isdigit(),最好用 unsigned char 值调用:

    const unsigned char *digit = argv[1];
    while (isdigit(*digit)) {
      digit++;
    }

验证此类输入的正确方法是使用 strspn 并检查它是否吃掉了整个字符串。在几乎所有情况下,strspn 都比任何 hand-coded 版本都快,除非您将验证和转换结合起来。

这是你的代码,其中的验证函数正是这样做的:

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

int validate_arg(const char *s) {
  size_t len;
  len = strspn(s, "0123456789");
  return len > 0 && !s[len];
}

int main(int argc, string argv[]) {
  int n = atoi(argv[1]);

  if (argc == 2 && validate_arg(argv[1])) {
    printf("Success\n%s\n", argv[1]);
    return 0;
  } else {
    printf("Usage: ./caesar key\n");
    return 1;
  }
}

Side-note:最好用无符号类型,用strtoul.

转换数