C if 语句,检查特殊字符和字母的最佳方法

C if statement, optimal way to check for special characters and letters

大家好,在此先感谢您的帮助,我正在学习 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(*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 returns 成功读取和分配的项目数 - 在这种情况下,您期望 return 值是 01(因为只有 junk 被分配给)。

综合起来,

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

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

编辑

所以,chqrlie 指出了我的一个大错误 - 这个测试不会按预期进行。

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

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

如果input[i]以非字母或非下划线字符开始,那么您匹配失败并且sscanf 退出,returning 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" );
  }
}

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

再分析一下sscanf(input[i], "%*[A-Za-z_]%c", &junk)的return值:

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

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

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] == '[=10=]') {
            printf("test passes: %d digits\n", ndigits);
        } else {
            printf("test fails\n");
            usrCooperation = 0;
        }
    }
    return usrCooperation;
}

check if the string from the main function parameter string argv[] is indeed a number

测试 字符串 是否转换为 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", &junk) 是测试数值转换的错误方法。

我遵循了用户“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;
       } */
}