【问题标题】:C if statement, optimal way to check for special characters and lettersC if 语句,检查特殊字符和字母的最佳方法
【发布时间】:2021-08-03 22:25:43
【问题描述】:

大家好,提前感谢您的帮助,我正在学习 CS50 课程,我刚刚开始编程。

我正在尝试检查主函数参数string argv[] 中的字符串是否确实是一个数字,我搜索了多种方法。 我在另一个主题How can I check if a string has special characters in C++ effectively? 中找到了用户Jerry Coffin 发布的解决方案:

char junk;
if (sscanf(str, "%*[A-Za-z0-9_]%c", &junk))
    /* it has at least one "special" character
else
    /* no special characters */

如果在我看来它可能适用于我正在尝试做的事情,我不熟悉 sscanf 函数,我很难集成和适应我的代码,我来了到目前为止我无法理解我的错误逻辑:

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

int numCheck(string[]);

int main(int argc, string argv[]) {
    //Function to check for user "cooperation"
    int key = numCheck(argv);
}

int numCheck(string input[]) {
    int i = 0;
    char junk;
    bool usrCooperation = true;

    //check for user "cooperation" check that key isn't a letter or special sign
    while (input[i] != NULL) {
        if (sscanf(*input, "%*[A-Za-z_]%c", &junk)) {
            printf("test fail");
            usrCooperation = false;
        } else {
            printf("test pass");
        }
        i++;
    }
    return 0;
}

【问题讨论】:

  • if (sscanf(str, "%*[A-Za-z0-9_]%c", &amp;junk)) 将是 true 成功读取和 EOF.- 很可能不是你想要的。
  • 请用cs50标记您的问题。 string 类型是非标准的。本课程正在教坏习惯。
  • 在我们尝试回答您的错误的性质之前,您先解释一下您的错误的表现如何?你如何运行程序,你希望看到什么,你实际看到了什么?
  • 我会使用for 循环和isdigit()。保持简单。

标签: c cs50


【解决方案1】:

检查主函数参数字符串argv[]中的字符串是否确实是一个数字

测试字符串是否转换为int的直接方法是使用strtol()。这很好地处理了“123”、“-123”、“+123”、“1234567890123”、“x”、“123x”、“”。

int numCheck(const char *s) {
  char *endptr;
  errno = 0; // Clear error indicator
  long num = strtol(s, &endptr, 0);
  if (s == endptr) return 0; // no conversion
  if (*endptr) return 0; // Junk after the number
  if (errno) return 0; // Overflow
  if (num > INT_MAX || num < INT_MIN) return 0; // int Overflow
  return 1; // Success
}

int main(int argc, string argv[]) {
  // Call each arg[] starting with `argv[1]`
  for (int a = 1; a < argc; a++) {
    int success = numCheck(argv[a]);
    printf("test %s\n", success ? "pass" : "fail");
  }  
}

sscanf(*input, "%*[A-Za-z_]%c", &amp;junk) 是测试数值转换的错误方法。

【讨论】:

  • strtol 是正确的方法,如果 OP 想要检查数字值,包括符号和初始空格,如果他想要检查数字序列,strspn() 更合适。
【解决方案2】:

您将argv 传递给numcheck 并测试其中的所有字符串:这是不正确的,因为argv[0] 是正在运行的可执行文件的名称,因此您应该跳过此参数。另请注意,您应该将input[i] 传递给sscanf(),而不是*input。

另外,我们来分析sscanf(input[i], "%*[A-Za-z_]%c", &amp;junk)的返回值:

  • 如果输入字符串为空,则返回EOF,
  • 如果%*[A-Za-z_] 失败,则返回0,
  • 如果在%*[A-Za-z_] 成功后转换%c 失败,它也会返回0,
  • 它返回1 是两个转换都成功。

此测试不足以检查字符串中的非数字,它实际上并没有提供有用的信息:对于字符串 "1" 和字符串 "a",返回值将是 0...

sscanf() 是非常棘手的,充满了怪癖和陷阱。绝对不是模式匹配的正确工具。

如果目标是检查字符串是否只包含数字(至少一个),请改用它,使用经常被忽视的标准函数strspn():

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

int numCheck(char *input[]) {
    int i;
    int usrCooperation = 1;

    //check for user "cooperation" check that key isn't a letter or special sign
    for (i = 1; input[i] != NULL; i++) {
        // count the number of matching character at the beginning of the string
        int ndigits = strspn(input[i], "0123456789");
        // check for at least 1 digit and no characters after the digits
        if (ndigits > 0 && input[i][ndigits] == '\0') {
            printf("test passes: %d digits\n", ndigits);
        } else {
            printf("test fails\n");
            usrCooperation = 0;
        }
    }
    return usrCooperation;
}

【讨论】:

    【解决方案3】:

    让我们再试一次:

    这仍然是你的问题:

    if (sscanf(*input, "%*[A-Za-z_]%c", &junk))
    

    但不是因为我最初说的原因 - *input 等于 input[0]。你想要那里有什么

    if ( sscanf( input[i], "%*[A-Za-z_]%c", &junk ) )
    

    您正在做的是在 while 循环中循环遍历所有命令行参数:

    while( input[i] != NULL )
    

    但你实际上只是在测试input[0]。

    所以,快速入门sscanf:

    第一个参数 (input) 是您正在扫描的字符串。这个参数的类型需要是char *(指向char的指针)。 string typedef 名称是 char * 的别名。 CS50 试图掩盖 C 字符串处理和 I/O 的粗略部分,string typedef 是其中的一部分,但它是 CS50 课程独有的,而不是语言的一部分。小心。

    第二个参数是格式字符串。 %[ 和 %c 是格式说明符,告诉 sscanf 你在字符串中寻找什么。 %[ 指定一组称为扫描集的字符 - %[A-Za-z_] 表示“匹配任何大小写字母和下划线序列”。 %*[A-Za-z_] 中的 * 表示不要将扫描结果分配给参数。 %c 匹配任何字符。

    剩余的参数是您要存储的输入项,它们的类型必须与格式说明符匹配。 %[ 期望其对应的参数具有 char * 类型并且是存储输入的数组的地址。 %c 期望其对应的参数(在本例中为 junk)也具有 char * 类型,但它期望单个 char 对象的地址。

    sscanf 返回成功读取和分配的项目数 - 在这种情况下,您期望返回值是 0 或 1(因为只有 junk 被分配到)。

    把它们放在一起,

    sscanf( input, "%*[A-Za-z_]%c", &junk )
    

    将从input 中读取并丢弃字符,直到它看到字符串终止符或不是 部分扫描集的字符。如果它看到不属于扫描集的字符(例如数字),则该字符被写入junk 并且sscanf 返回1,在此上下文中被视为“真”。如果它没有看到扫描集之外的任何字符,则不会向junk 写入任何内容,并且sscanf 返回0,这被视为“假”。

    编辑

    所以,chqrlie 指出了我的一个大错误——这个测试不会按预期工作。

    如果input[i] 中没有非字母和非下划线字符,则不会为junk 分配任何内容,sscanf 返回 0(未分配任何内容)。如果input[i] 以字母或下划线开头,但稍后包含非字母或非下划线字符,则该错误字符将被转换并分配给junk,sscanf 将返回1。

    到目前为止一切顺利,这就是您想要发生的事情。但是……

    如果input[i] 以非字母或非下划线字符开头,那么你有一个匹配失败并且sscanf退出,返回0。所以它会错误地匹配错误的输入。

    坦率地说,这不是测试“坏”字符是否存在的好方法。

    一个可能更好的方法是使用这样的东西:

    while ( input[i] )
    {
      bool good = true;
    
      /**
       * Cycle through each character in input[i] and
       * check to see if it's a letter or an underscore;
       * if it isn't, we set good to false and break out of 
       * the loop.  
       */
      for ( char *c = input[i]; *c; c++ )
      {
        if ( !isalpha( *c ) && *c != '_' )
        {
          good = false;
          break;
        }
      }
    
      if ( !good )
      {
        puts( "test fails" );
        usrCooperation = 0;
      }
      else
      {
        puts( "test passes" );
      }
    }
    

    【讨论】:

    • 鉴于参数input 被定义为string input[],input[0] 确实是char *,所以问题不在于你描述的地方。然而检查argv[0] 似乎不正确。
    • 我在发表*input 评论时已经投了赞成票,而且它似乎有额外的维度。 CS50 听起来像是要远离的东西:-)
    • 还有一个问题:您对sscanf() 行为的描述不完整:如果扫描集中没有字符出现在字符串的开头,则初始转换失败并且sscanf() 返回0 ,除非字符串为空且sscanf() 返回EOF。换句话说,"1" 和"a" 的返回值将是0,所以测试没有用。
    • Re “%[A-Za-z_] 表示……”:C 标准没有定义 A-Z 或 a-z 的任何含义,只是实现必须定义它。 C 2018 7.21.6.2 12 说“......如果 - 字符在扫描列表中并且不是第一个字符,也不是第一个字符是 ^ 的第二个字符,也不是最后一个字符,则行为是实现定义的。”
    • @chqrlie:看,这就是我在应该工作的时候在 SO 上胡闹的结果。我会想出一个更好的解释并更新我的答案......在某个时候。
    【解决方案4】:

    我遵循用户“chux - Reinstate Monica”的解决方案。谢谢大家帮我解决这个问题。这是我的最终程序,也许它可以帮助将来的其他学习者。我决定避免使用非标准库“cs50.h”。

    //#include <cs50.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <limits.h>
    
    void keyCheck(int);
    int numCheck(char*);
    
    int main(int argc, char* argv[])
    {
        //Error code == 1;
        int key = 0;
    
        keyCheck(argc); //check that two parameters where sent to main.
        key = numCheck(argv[1]); //Check for user "cooperation".
    
        return 0;
    }
    
    
    //check for that main received two parameters.
    void keyCheck(int key)
    {
        if (key != 2) //check that main argc only has two parameter. if not terminate program.
        {
            exit(1);
        }
    }
    
    
    //check that the key (main parameter (argv [])) is a valid number.
    int numCheck(char* input)
    {
        char* endptr;
        errno = 0;
        long num = strtol(input, &endptr, 0);
    
        if (input == endptr) //no conversion is possible.
        {
            printf("Error: No conversion possible");
            return 1;
        }
    
        else if (errno == ERANGE) //Input out of range
        {
            printf("Error: Input out of range");
            return 1;
        }
    
        else if (*endptr) //Junk after numeric text
        {
            printf("Error: data after main parameter");
            return 1;
        }
    
        else //conversion succesfull
        {
            //verify that the long int is in the integer limits.
            if (num >= INT_MIN && num <= INT_MAX)
            {
                return num;
            }
            //if the main parameter is bigger than an int, terminate program
            else
            {
                printf("Error key out of integer limits");
                exit(1);
            }
        }
    
        /* else
           {
               printf("Success: %ld", num);
               return num;
           } */
    }
    

    【讨论】:

    • return num 将long 缩小到int。最好使用long numCheck (char *input) 或检查num 是否在int 范围内。
    猜你喜欢
    • 2015-10-27
    • 2022-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-01
    • 2015-05-31
    • 2015-12-02
    相关资源
    最近更新 更多