这是一个在 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; NULL 被求值,但值没有保存在任何地方,所以这也是一个无所事事的声明。
for (i = 0; s[i] != '\0';)
{
++i; /* do the step part as the loop body */
}
在这个例子中,在测试之后,循环体被运行;这增加了i。那么循环的“step”部分就省略了。
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);
}
它简短而简洁,但是一旦你习惯了这些东西就会很清楚。