如何只计算字符串中的字母?

How to count only letters in string?

我正在尝试编写一个计算字符串中多个元素的程序。第一个是字母。

该作业是 CS50 第 2 周问题集的一部分,因此包含库。

使用 while 条件我能够计算出每个字符,但是一旦我添加了 isalnum(检查字符是否为字母数字),代码就停止工作了。

我做错了什么?

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

int main(void) {
    string text = get_string("Text: ");
    printf("%s, \n", text);

    int letters = 0;
    while (text[letters] != '[=10=]') {
        if (isalnum(text[letters])) {
            letters++;
        }
    }
    printf("%i \n", letters);
}

此处显示了如何定义正确的循环

size_t letters = 0;
for ( size_t i = 0; text[i] !='[=10=]'; i++ )
{
    if ( isalnum( ( unsigned char )text[i] ) )
    {
       letters++;
    }
}

printf( "%zu\n", letters );

如果你想使用 while 循环,那么它可以看起来像

size_t letters = 0;
size_t i = 0;
while ( text[i] !='[=11=]' )
{
    if ( isalnum( ( unsigned char )text[i++] ) )
    {
       letters++;
    }
}

printf( "%zu\n", letters );

注意函数isalnum检测字母和数字。如果你只需要计算字母,那么使用函数 isalpha.

你说是字母,然后你用isalnum? 所以在你的情况下数字将被算作字母 然后你增加循环中字母的值 仅当它是字母时才对字符串进行迭代 这样当条件为假时会导致无限循环 这是一个解决方案:

#include <stdio.h>
#include <ctype.h>

int countLetters(char *str) {
  int letters = 0, ind = 0;
  while(str[ind] != '[=10=]') 
    isalpha((unsigned char)str[ind++]) && letters++;
  return letters;
}

int main(void) {
  char text[100];
  fgets(text, sizeof(text), stdin);
  printf("%i", countLetters(text));
  return 0;
}
// input: hello 15-p */x
// output: 7

如果你只想计算字母:

size_t count_letters(const char *str)
{
    size_t count = 0;

    while(*str)
    {
        count += !!isalpha(*str++);
    }

    return count;
}

或者如果你喜欢 for 循环

size_t count_letters_for_loop(const char *str)
{
    size_t count = 0;

    for(; *str; str++)
    {
        count += !!isalpha(*str);
    }

    return count;
}

正如大卫建议的那样:

!! 运算在逻辑上取反两倍的值。由于任何逻辑运算给出 0 或 1,此双重否定给出 1 任何非零值或 0 零。它需要作为 isalpha 函数 Returns non-zero value if c is an alphabet, else it returns 0.

尝试将这样的逻辑移动到单独的函数中。这是C语言

中非常重要的技能

while 循环有一个缺陷:你使用同一个变量来计算字母数字字符和索引字符串。它仅在所有字符都是字母数字时才有效,否则会出现无限循环,因为您停止递增 letter.

您应该使用索引为 ifor 循环。

另请注意 isalnum() 来自 <ctype.h> 的所有函数均未定义负值,但 EOF 除外。在 char 默认签名的平台上,字符串中的某些字符可能具有负值,如果将它们传递给 isalnum() 会导致未定义的行为。您应该将 char 值转换为 (unsigned char) 以避免这种情况。

这是修改后的版本。

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

int main(void) {
    string text = get_string("Text: ");
    printf("%s, \n", text);

    int letters = 0;
    for (int i = 0; text[i] != '[=10=]'; i++) {
        if (isalnum((unsigned char)text[i])) {
            letters++;
        }
    }
    printf("%i\n", letters);
    return 0;
}

正如您所发现的,由于并非字符串中的所有字符都保证是字母数字字符,因此您使用 letters 作为计数器和索引是有缺陷的。当您遇到一个不是 alpha 且不是数字的字符时,if (isalnum(text[letters])) 测试为 false 并且 letters 永远不会递增,从而导致此时出现无限循环。 (你在下一次迭代中再次测试同一个角色——结果相同——而且场景永远不会改变....)

正如所有其他非常好的和非常正确的答案所建议的那样,只需使用一个单独的循环计数器变量(或指针)并递增它以迭代您的字符串。

你可以做的另一件事来验证你的逻辑(同时输出 isalnum() 字符)就是简单地取消用 printf 重新打印原始字符串(你在你从你的条目),而是输出符合你的标准的字符。例如:

    for (int i = 0; text[i]; i++) {
        if (isalnum((unsigned char)text[i])) {
            putchar (text[i]);
            letters++;
        }
    }

这只是您输出的一个细微变化,它具有双重作用,既可以提供您的输出,也可以确认每个符合所用标准的字符。

另请注意,没有必要 #include <string.h>,因为您的代码中没有需要包含它的函数。有了这些变化,一个简单的例子可能是:

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

int main(void) {

    int letters = 0;
    string text = get_string("Text: ");

    for (int i = 0; text[i]; i++) {
        if (isalnum((unsigned char)text[i])) {
            putchar (text[i]);
            letters++;
        }
    }

    printf (", %d\n", letters);
}

例子Use/Output

$ ./bin/ltrcountcs50-1
Text: 123.abc-456_def_*.*_789
123abc456def789, 15

将计数移入函数

您可以轻松地将字母数字字符的计数移动到一个函数中。将字符串传递给函数的一个好处是函数接收到的指针是 main() 指针的副本(C 是按值传递)。这使您可以简单地使用参数和 return 字母数字字符的计数进行迭代,例如

int countalnum (const char *s)
{
    int letters = 0;

    while (*s)
        if (isalnum((unsigned char)*s++))
            letters++;

    return letters;
}

(注意: 您必须将参数作为 const char * 传递才能使用指向常量 char 的指针。您不能通过 cs50 typedef 使用 const string。如果您不更改函数中传递的值,则传递为 const 允许编译器进行优化,否则无法进行)

使用上面的函数,您的代码将减少为:

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

int countalnum (const char *s)
{
    int letters = 0;

    while (*s)
        if (isalnum((unsigned char)*s++))
            letters++;

    return letters;
}

int main(void) {

    string text = get_string("Text: ");

    printf ("%s, %d\n", text, countalnum(text));
}

例子Use/Output

$ ./bin/ltrcountcs50-1
Text: 123.abc-456_def_*.*_789
123.abc-456_def_*.*_789, 15

但是在这里,使用该函数会导致在 main() 中打印整个原始字符串。您可以根据需要调整输出。