检查输入是否为字符串的更好方法是什么?

时间:2015-05-28 20:33:30

标签: c string validation

我正在制作一个程序,其中我要求输入用户名,并且我只想接受只有有效字符的字符串(字母)。

我发现我可以使用

    do{
       //since scanf returns the number of currectly input
       if(scanf("%s", &name) == 1)
           break;
       else printf("Please enter a valid name.\n);
    }while(1);

    do{
       check = 0;
       scanf("%s", &name);
       for(i=0; i<strlen(name; i++){
          //since isalpha() returns != 0 if it's a letter
          if(isalpha(name[i]) == 0){
             printf("Invalid character. Please enter a valid name.\n");
             check = 1;
             break;
          }
       }
    }while(check == 1);

但是我不确定这些是否有效,除了字母之外还有什么更好的检查。 关于在小写字母上输入所有输入字母(在此验证之后)并使用

制作第一个字母大写的方法
    //all to lower except the first letter
    for(i=1; i<strlen(name); i++){
       name[i] = tolower(name[i]);
    }
    //first letter to upper
    name[0] = toupper(name[i]);
    x=1;
    while(name[x] != '\0'){
       //if the letter before is a white space, even the first letter, it should place the first letter of a name upper
       if(name[x-1] == ' ')
          name[x] = toupper(name[x]);
       x++;
    }

这会有用吗?

3 个答案:

答案 0 :(得分:3)

if(scanf("%s", &name)将所有非空白区域(而不仅仅是字母)读入name,如果输入仅为"\n",则不会返回。

if(isalpha(name[i]) == 0){循环不错,但如果输入仅为scanf("%s", &name)或仅为空格,则"\n"仍然不会返回。

for(i=1; i<strlen(name); i++) name[i] = tolower(name[i])可以使所有后续字母小写,但如果代码重复计算字符串长度则效率低。

分开读取数据和解析数据。使用fgets()读取数据和各种代码以测试数据的正确性。

char buf[200];
fgets(buf, sizeof buf, stdin);

int n = 0;
// Skip leading white-space
// Look for A-Z, a-z or space  (like a space between first & last)
// Skip white-space like \n
// Save into 'n' the current scan position
sscanf(buf, " %*[A-Za-z ] %n", &n);
if (n > 0 && buf[n] == '\0') Success();  // @user3121023

代码是否需要清除buf潜在的"\n",建议:

buf[strcspn(buf, "\n")] = 0;

答案 1 :(得分:0)

让我们看看每个选项。

第一个选项:

do {
   //since scanf returns the number of currectly input
   if(scanf("%s", &name) == 1)
       break;
   else printf("Please enter a valid name.\n");
} while(1);

这不会像你期望的那样完成。首先,name究竟是什么?我几乎肯定你需要scanf("%s", name)而不是name而不是&name),除非你宣布它为char name;,无论如何都会是灾难性的。

无论如何,我用这种方法看到的问题是你没有真正验证字符串。阅读有关%s的手册页部分:

  

s - 匹配一系列非空白字符;下一个指针   必须是一个指向字符数组的指针,该指针足够长以容纳   输入序列和终止空字节('\ 0'),即   自动添加。输入字符串在空格处或在空格处停止   最大字段宽度,以先到者为准。

没有什么说字符串只由字母字符组成。

第二个选项:

do{
   check = 0;
   scanf("%s", &name);
   for(i=0; i<strlen(name); i++){
      //since isalpha() returns != 0 if it's a letter
      if(isalpha(name[i]) == 0){
         printf("Invalid character. Please enter a valid name.\n");
         check = 1;
         break;
      }
   }
}while(check == 1);

同样,您可能需要name而不是&name。您也不应该在strlen()循环条件中调用for,因为它效率低(strlen()是O(n))。智能编译器可能会对其进行优化,但编译器很难知道何时可以安全地进行优化。只需在循环前调用strlen()并将结果存储在变量中。

isalpha()期望一个整数作为参数,预计可以是EOFunsigned char转换为int。同样,您没有显示name的声明,但假设它是一个字符数组,您应该在调用name[i]之前将unsigned char强制转换为isalpha(),以便您没有任何标志延伸意外:

if (isalpha((unsigned char) name[i]) == 0) { /* ... */ }

事实上,如果你使用普通ctype调用任何char系列宏/函数,gcc现在很可能会给你一个警告。这些宏是故意编写的,显示警告,正好,因为这是一个常见的错误。它是实现定义的普通char是签名还是未签名。由于符号扩展,你会在带有签名字符的平台中遇到问题(这是因为通常使用查找表来实现isalpha()之类的东西,并且扩展符号会产生一个负数,它将使查找表的索引为负数index - 糟糕!)

除此之外,这种方法对我来说似乎没问题。

第三种,也许是更好的选择:

由于您提到fgets(),我认为您可以通过将fgets()sscanf()相结合来轻松完成此操作。首先,您阅读fgets()行。然后,使用sscanf()匹配仅包含[a-zA-Z]范围内的字符的字符串。这可以使用格式说明符%[a-zA-Z]s来完成。然后,您只需检查这是否与整条线匹配。这是一个工作计划:

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

int main(void) {
    static char buf[512];
    static char name[512];

    int is_valid = 0;

    while (!is_valid) {
        fgets(buf, sizeof(buf), stdin);

        size_t line_len = strlen(buf);
        if (line_len > 0 && buf[line_len-1] == '\n') {
            buf[line_len-1] = '\0';
            line_len--;
        }

        int n = 0;
        if (sscanf(buf, " %[a-zA-Z] %n", name, &n) == 1 && buf[n] == '\0') {
            is_valid = 1;
        } else {
            printf("Please enter a valid name.\n");
        }

    }

    printf("Name: %s\n", buf);

    return 0;
}

确保缓冲区足够大;对于任意长的名称/行,此代码容易受到缓冲区溢出的影响。

现在让我们看一下使第一个字母大写的代码:

//all to lower except the first letter
for(i=1; i<strlen(name); i++){
   name[i] = tolower(name[i]);
}
//first letter to upper
name[0] = toupper(name[i]);
x=1;
while(name[x] != '\0'){
   //if the letter before is a white space, even the first letter, it should place the first letter of a name upper
   if(name[x-1] == ' ')
      name[x] = toupper(name[x]);
   x++;
}

再次,从循环条件中删除strlen()toupper()tolower()也期望int作为参数,代表转化为EOF的{​​{1}}或unsigned char。您应该将其转换为int以避免可能的符号扩展问题,正如我之前在其他示例中所述。

这是错误的:

unsigned char

应该是:

//first letter to upper
name[0] = toupper(name[i]);

//first letter to upper name[0] = toupper(name[0]); 的参数为toupper(),而不是name[0]

最后,这没用:

name[i]

x=1; while(name[x] != '\0'){ //if the letter before is a white space, even the first letter, it should place the first letter of a name upper if(name[x-1] == ' ') name[x] = toupper(name[x]); x++; } 永远不会给你一个带空格的字符串(请参阅我上面粘贴的联机帮助页引用)。

答案 2 :(得分:0)

假设您希望您的名字只包含字符a到z或A到Z,那么您可以使用此功能

multifield