Here is a nice simple example of looping over a string in 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));
}
Look at just the for
loop. There are three parts: initialization, test, and the step. Then there is either a single statement, or else a "block" (a set of zero or more statements enclosed in curly braces). In my example, the block is empty (other than a human-readable comment that doesn't execute).
The initialization is executed once and should be used to set up the loop somehow; in my example, it's used to set i
to zero. The "test" is some expression that is evaluated before the loop body is executed; if the test evaluates false, the loop terminates, so a condition that is false before the loop even does anything will cause the loop body to never be executed. If the test evaluates true, the loop body is executed, and after that the "step" is executed; the "step" should advance the loop somehow. In my example, the test is checking to see whether the loop has found the terminating NUL byte yet, and the step increments the loop counter i
.
So think about how this loop works. We start i
at zero, then the loop immediately checks to see if the first character in the string is a NUL byte. If it is, the loop is already over, and our length()
function returns 0, which is correct! If the first byte of a string is a terminating NUL byte, then that is a "null string" and the correct length is 0. (It's important when writing loops to think about what happens if the loop does nothing. Loops should "do nothing" correctly; this one does.)
An interesting thing about C's for
loop: all the parts of the loop are optional. Here are some alternate versions of the loop; these will all work.
i = 0; /* initialize i before loop */
for (; s[i] != '\0'; ++i)
; /* looks weird but this is a statement that does nothing */
In this example, we initialize i
outside the loop, and the initialization part is left empty. The empty statement with a single semicolon is unusual but legal. More often, you will see someone put something like this: NULL;
The NULL
is evaluated, but then the value isn't saved anywhere, so this is also a do-nothing statement.
for (i = 0; s[i] != '\0';)
{
++i; /* do the step part as the loop body */
}
In this example, after the test, the loop body is run; this increments i
. Then the "step" part of the loop is omitted.
i = 0; /* initialization */
for (;;)
{
if (s[i] == '\0') /* test */
break;
++i; /* step */
}
In this example, all three parts of the for
loop are omitted, which is legal and basically means "loop forever until something stops the loop". Then in the if
statement, if we see the NUL byte we execute the break
statement, which terminates the loop. Finally we increment i
.
Note that the initialization, test, and step are actually present; they just aren't in the for
loop line. For simple loops like this one, I recommend the standard form, not this weird form.
Finally, some people will write a tricky loop that increments the character pointer itself, rather than incrementing a loop variable like i
. Here's an example:
int length(char const *s)
{
char const *start;
for (start = s; *s != '\0'; ++s)
{
}
return (s - start);
}
In this example, we increment the variable s
itself. Since it was passed as an argument, the function has its own copy and it can modify that copy without affecting anything else outside the function. This saves a copy of the initial pointer, increments until the terminating NUL is found, then subtracts the start position from the new position to find the length.
Usually people will shorten the loop. The test is true if the test expression is non-zero, and in a string, only the NUL byte is zero. So the test expression can simply be *s
and it will evaluate true if the current position is not a NUL byte:
int length(char const *s)
{
char const *start;
for (start = s; *s; ++s)
{
}
return (s - start);
}
And finally, we could make this a bit shorter with a while
loop:
int length(char const *s)
{
char const *start = s;
while (*s)
++s;
return (s - start);
}
It's short and terse, but it's pretty clear once you are used to this stuff.