这是在 C 中循环字符串的一个很好的简单示例。
#include <stdio.h>
int length(char const *s)
{
int i;
for (i = 0; s[i] != '\0'; ++i)
{
/* loop body is empty */
}
return i;
}
int main(int argc, char *argv[])
{
char const test[] = "hello";
printf("String: '%s' length: %d\n", test, length(test));
}
只看for
循环。分为三个部分:初始化、测试和步骤。然后有一个语句,或者一个“块”(一组零个或多个用花括号括起来的语句)。在我的示例中,该块是空的(除了不执行的人类可读注释)。
初始化执行一次,应该用于以某种方式设置循环;在我的示例中,它用于设置i
为零。“测试”是在循环体执行之前评估的一些表达式;如果测试结果为假,则循环终止,因此在循环执行任何操作之前为假的条件将导致循环体永远不会被执行。如果测试结果为真,则执行循环体,然后执行“步骤”;“步骤”应该以某种方式推进循环。在我的示例中,测试正在检查循环是否已找到终止 NUL 字节,并且该步骤会增加循环计数器i
。
所以想想这个循环是如何工作的。我们i
从零开始,然后循环立即检查字符串中的第一个字符是否为 NUL 字节。如果是,则循环已经结束,我们的length()
函数返回 0,这是正确的!如果字符串的第一个字节是终止 NUL 字节,那么这是一个“空字符串”并且正确的长度为 0。(在编写循环时考虑如果循环什么都不做会发生什么是很重要的。循环应该“什么都不做" 正确;这个可以。)
关于 C 循环的一个有趣的事情是for
:循环的所有部分都是可选的。以下是循环的一些替代版本;这些都会起作用。
i = 0; /* initialize i before loop */
for (; s[i] != '\0'; ++i)
; /* looks weird but this is a statement that does nothing */
在这个例子中,我们i
在循环外进行初始化,初始化部分留空。带有单个分号的空语句不常见但合法。更多的时候,你会看到有人这样写: NULL;
TheNULL
被求值,但是值没有被保存到任何地方,所以这也是一个无所事事的声明。
for (i = 0; s[i] != '\0';)
{
++i; /* do the step part as the loop body */
}
在这个例子中,在测试之后,循环体被运行;这增加了i
。然后省略循环的“步骤”部分。
i = 0; /* initialization */
for (;;)
{
if (s[i] == '\0') /* test */
break;
++i; /* step */
}
在这个例子中,循环的所有三个部分for
都被省略了,这是合法的,基本上意味着“永远循环,直到有东西停止循环”。然后在if
语句中,如果我们看到 NUL 字节,我们将执行该break
语句,从而终止循环。最后我们增加i
.
请注意,初始化、测试和步骤实际上是存在的;他们只是不在for
循环线中。对于像这样的简单循环,我推荐标准形式,而不是这种奇怪的形式。
最后,有些人会编写一个棘手的循环来递增字符指针本身,而不是像i
. 这是一个例子:
int length(char const *s)
{
char const *start;
for (start = s; *s != '\0'; ++s)
{
}
return (s - start);
}
在这个例子中,我们增加了变量s
本身。由于它是作为参数传递的,因此该函数有自己的副本,它可以修改该副本而不影响函数之外的任何其他内容。这保存了初始指针的副本,递增直到找到终止 NUL,然后从新位置减去起始位置以找到长度。
通常人们会缩短循环。如果测试表达式不为零,则测试为真,并且在字符串中,只有 NUL 字节为零。所以测试表达式可以很简单*s
,如果当前位置不是 NUL 字节,它将评估为真:
int length(char const *s)
{
char const *start;
for (start = s; *s; ++s)
{
}
return (s - start);
}
while
最后,我们可以通过循环使它更短一些:
int length(char const *s)
{
char const *start = s;
while (*s)
++s;
return (s - start);
}
它简短而简洁,但是一旦您习惯了这些东西,就会很清楚。