【问题标题】:C sscanf to validate input formatC sscanf 验证输入格式
【发布时间】:2020-04-09 12:10:53
【问题描述】:

我想写一个程序来读取用户输入的一行,格式如下:<Name>,<Age>,<City>

  • 名称只能包含英文字母、空格和-
  • 年龄必须是 18 到 120 之间的整数。
  • 城市只能包含英文字母和-

每个都可以是 49 号。 我想存储信息并打印信息错误将打印错误输入。

我的代码如下:

    char str[150];
    char input[3][50] = { 0 };
    int num = 0;
    if (fgets(str, 150, stdin) != NULL) {
        num = sscanf(str, "%[a-zA-Z -],%[0-9],%[a-zA-Z-]", input[0], input[1], input[2]);
    }
    if (num < 3) {
        printf("ERROR\n");
    }

问题在于,对于 Name1$#,20,NYBest,19,Rome123Best,100,Paris1$ 等输入,将不会打印错误,其中城市格式错误(带有尾随字符)。有什么方法可以使用sscanf 解决它?

【问题讨论】:

  • 注意:%[...] 说明符不是正则表达式。
  • char str[150]; 太短了,你需要最少 49+1+49+1+49+1+1 (倒数第二个 +1 是换行符,最后一个 +1 是字符串终止 @ 987654331@ 字符)。好吧,除非您注意阅读此处显示的代码之外的换行符。
  • @pmg:另外,%[…] 说明符中范围的使用取决于实现。

标签: c scanf


【解决方案1】:

您可以使用sscanf() 和字符类来满足您的目的,但是您的格式字符串存在一些小问题:

  • A-Z 采用 ASCII 编码或至少是字母连续的编码。
  • 结尾的- 有特殊含义,将破折号放在首位以明确匹配破折号。
  • 没有长度前缀,因此超过 49 个字符的名称或城市将溢出目标数组。

而不是使用fgets(),您应该手动读取行以检测过长的行。

您可以添加一个额外的%c 来检查行尾是否有多余的字符。如果您不打算使用字段值,则不需要存储转换后的值,但您必须转换数字以检查其值是否在请求的范围内:

    char str[150];
    char name[50];
    char city[50];
    char agestr[4];
    size_t i;
    int c, age, pos, n;

    for (i = 0; (c = getchar()) != EOF && c != '\n'; i++) {
        if (i < sizeof(str) - 1)
            str[i] = (char)c;
    }
    if (c == EOF && i == 0) {
        printf("end of file\n");
        return -1;
    }
    if (i >= sizeof(str)) {
        printf("line too long\n");
        return 0;
    }
    str[i] = '\0';
    pos = 0;
    /* validate the name */
    if (sscanf(str + pos, "%49[-a-zA-Z ]%n", name, &n) != 1 || str[pos + n] != ',') {
        printf("invalid name\n");
        return 0;
    }
    pos += n + 1;
    /* validate the age */
    if (str[pos] == '0' || sscanf(str + pos, "%3[0-9]%n", agestr, &n) != 1 || str[pos + n] != ',') {
        printf("invalid age\n");
        return 0;
    }
    age = atoi(agestr);
    if (age < 18 || age > 120) {
        printf("age out of range: %d\n", age);
        return 0;
    }
    pos += n + 1;
    /* validate the city */
    if (sscanf(str + pos, "%49[-a-zA-Z]%n", city, &n) != 1 || str[pos + n] != '\0') {
        printf("invalid city\n");
        return 0;
    }
    /* Input was validated... proceed */

【讨论】:

  • +1,但是if 语句中的条件非常残酷,并且通过使用strtol() 而不是atoi()age 添加适当的错误检查会使情况变得更糟。 IMO 这对于一些一次性代码来说是可以的,但它不是我想要在一些代码库中拥有的,只是等待几年后遇到一些新的维护者。 “我们需要在数据中添加一个地址……”
  • @AndrewHenle:我同意可读性问题并且可能会解决这个问题,但是关于atoi(),没有问题,因为agestr 最多有来自sscanf() 格式字符串的3 位数字。
  • @chqrlie:如果缺少换行符,该测试将神秘地失败。从技术上讲,这是合法的,但似乎%n 在这里是一个更好的选择。 (检查字符 n\n\0;以后可以通过允许 , 来扩展)
  • @chqrlie:是的,您还必须检查n 的值。幸运的是,这并不太难。 (这就是为什么我更喜欢getline 而不是fgets。)
  • @rici:我没有使用fgets()getline(),而是更新了答案以读取整行,丢弃多余的长度并出现错误。我不将换行符存储在缓冲区中。额外的%c 应该会导致转换失败。
猜你喜欢
  • 1970-01-01
  • 2018-06-11
  • 1970-01-01
  • 2014-10-29
  • 1970-01-01
  • 1970-01-01
  • 2017-03-04
  • 1970-01-01
相关资源
最近更新 更多