在C语言中统计字符串中某个字符出现的次数

5

我刚开始学习C语言,现在正在编写一个像explode一样的函数。我想要计算一个指定字符在字符串中出现的次数。

int count_chars(char * string, char * chr)
{
    int count = 0;
    int i;

    for (i = 0; i < sizeof(string); i++)
    {
        if (string[i] == chr)
        {
            count++;
        }
    }

    return count;
}

每次它只返回0。请问有人能解释一下为什么吗? :)

毫不奇怪,这段代码会产生编译器警告。这应该让你意识到有些东西出了问题以及出了什么问题(只是其中的一个问题)。在编译时你是否收到了警告? - Skizz
4个回答

12

您的代码存在严重缺陷。以下是正确的写法:

int count_chars(const char* string, char ch)
{
    int count = 0;
    int i;

    // We are computing the length once at this point
    // because it is a relatively lengthy operation,
    // and we don't want to have to compute it anew
    // every time the i < length condition is checked.
    int length = strlen(string);

    for (i = 0; i < length; i++)
    {
        if (string[i] == ch)
        {
            count++;
        }
    }

    return count;
}

点击此处查看代码在示例输入上的运行结果.

你犯了以下错误:

  1. 由于你想要找到一个“字符”,第二个参数应该是一个字符(而不是一个char*),这会在后面产生影响(参见#3)。
  2. sizeof(string)并不能给出字符串的长度。它给出的是指针在你的架构中的大小(以字节为单位),这是一个常数值(例如,在32位系统上是4)。
  3. 你正在比较一些不是内存地址的值与chr指向的内存地址。这是在比较苹果和橙子,将始终返回false,因此if永远不会成功。
  4. 相反,你要做的是将一个字符string[i])与函数的第二个参数进行比较(这也是为什么第二个参数也是char的原因)。

以上内容的“更好”版本

下面的评论者们正确地指出了原始答案中不是C语言中通常使用的方式,可能会导致代码运行缓慢,甚至在(诚然非凡的)情况下可能会出现错误。

由于我认为count_chars的“正确”实现对于刚开始接触C语言的人来说可能太过复杂,因此我将在此附上它,同时保留原始答案。

int count_chars(const char* string, char ch)
{
    int count = 0;
    for(; *string; count += (*string++ == ch)) ;
    return count;
}
注意:我故意以这种方式编写循环,以此来说明在某个阶段必须划清可能和更好之间的界限。

查看此代码在示例输入上的运行结果


@R..:我完全同意你所有有根据的反对意见(我真诚地希望任何现代编译器都能优化 strlen 调用)。请注意,我正在回复一个新手,我不想放弃他们的所有代码,这会让他们更难理解他们做错了什么以及为什么。在我看来,上面的代码虽然微妙地有缺陷,但是它是通向知识之路上的一个合理的垫脚石。谢谢。 - Jon
@Rob:目前的版本是否令您满意,或者您认为它需要进行一些扩展? - Jon
每次循环都调用strlen函数,这真是高效!有趣的是,在DevStudio 2010发布模式下,strlen函数被内联了两次,一次在for循环之前,一次在末尾。 - Skizz
1
即使O(n)与O(n²)的讨论对听众来说毫无意义,也不能以此为借口教授会导致O(n)循环变成O(n²)的非常糟糕的实践。新程序员应该学习在C语言中测试string[i]!=0是循环遍历字符串的惯用方法。 - R.. GitHub STOP HELPING ICE
2
@Jon:绝对正确。如果你不打算学习C的惯用语,那么学习C就没有意义,因为用其他语言的惯用语写C只会给你带来两个世界中最糟糕的东西(C的所有危险和其他语言的低效率)。我认为在C中循环遍历字符串直到遇到空终止符是编写自己的字符串处理代码中最重要的惯用语之一(与仅进行库调用相比)。 - R.. GitHub STOP HELPING ICE
显示剩余7条评论

2

这是C语言!它被设计成简洁明了的语言!

int count_chars(const char* string, char ch)
{
  int c = 0;
  while (*string) c += *(string++) == ch;
  return c;
}

更新

我会尝试解释它的工作原理:

int c = 0;

这将是找到的匹配数量计数。
while (*string)

这是循环控制语句,只要条件为真,就会继续迭代。在本例中,条件是*string。在C语言中,字符串被存储为“空终止符”,这意味着字符串的最后一个字符是一个值为0('\0')的字符。 *string会被评估为指针所指向的字符。在C语言中,如果表达式的结果是非零值,则为“true”,如果结果为零,则为“false”。*string是一个表达式,因此任何由*string指向的非零字符都是true,而字符串末尾的'\0'则是false。因此,如果*string指向字符串的末尾,它将终止操作。
*(string++)

这是一个表达式,它计算指针所指向的值。 ++ 是后置自增运算符,因此指针的值向前移动一位,即它指向字符串中的下一个字符。请注意,在表达式评估完成后,表达式的值与 *string 的值不同,因为指针已经移动了。

*(string++) == ch

这是一个比较表达式,它将*string(更新之前的值)的值与ch的值进行比较。在C语言中,这个表达式的结果是一个整数(C语言没有bool类型),如果表达式为真,则值为'1',如果为假,则值为'0'。

c += *(string++) == ch;

我们知道在+=后面的位数,如果该字符是我们要查找的字符,则为'1',否则为'0'。 +=是以下简写形式:
c = c + (*(string++) == ch);

因此,如果找到匹配的字符,它将增加计数。

在这种特殊情况下,使用+=语法没有什么优势,但是如果c更复杂,比如*(variable [index].structure_member [index2]),那么它只会被评估一次。

末尾的;标记了语句的结束,并且因为在while后面没有{,所以也标记了while循环的结束。


我不知道你在这里做了什么。 - Rob
@Rob:我添加了一些说明性注释。 - Skizz
哇,非常好的解释。唯一我不喜欢的就是可读性,但我想任何有经验的C程序员应该能够轻松阅读吧? - Rob

2
您可能想使用一个实际获取字符串长度的函数,而不是使用sizeofsizeof将会获取数据类型的大小。它不会返回字符串的长度。而strlen会返回字符串的长度。

0

正如大家已经告诉你的答案,

1)你不能使用 sizeof,而是要用 strlen(string)来获取字符串长度。他们已经告诉了你原因。

2)我认为大家都忽略了这个问题,你在第二个参数中使用了 char 指针。虽然大家都建议你将其改为 chr,但如果你还想这么做的话,那么在循环中应该这样写:

if ( string(i)== *chr ) \\ not just ch remember you declared it as a pointer
                      ch gives the address but you want the character so use *ch

你还可以使用 strchr 函数。
   int count_chars (char *string, char ch)
       {
         int i;
        if(string=strchr(string,'s'))++i;
            while (string!=NULL)
               if(string=strchr(string+1,chr)
               ++i;
              return i;
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接