Flow Control and Conditions

Basic flow control is fairly simple and usually involves a condition in parentheses and a block of conditionally executed code in curly braces. These curly braces constitute a new scope, into which new variables can be introduced. So, for example:

if (x == 7) {
    int i = 0;
    i += 1;
}

After the closing curly brace in the fourth line, the i introduced in the second line has ceased to exist, because its scope is the inside of the curly braces. If the contents of the curly braces consist of a single statement, the curly braces can be omitted, but I would advise beginners against this shorthand, as you can confuse yourself. A common beginner mistake (which will be caught by the compiler) is forgetting the parentheses around the condition. The full set of flow control statements is given in K&R Chapter 3, and I’ll just summarize them schematically here (Example 1-1).

Example 1-1. The C flow control constructs

if (condition) {
    statements;
}

if (condition) {
    statements;
} else {
    statements;
}

if (condition) {
    statements;
} else if (condition) {
    statements;
} else {
    statements;
}

while (condition) {
    statements;
}

do {
    statements;
} while (condition);

for (before-all; condition; after-each) {
    statements;
}

The if...else if...else structure can have as many else if blocks as needed, and the else block is optional. Instead of an extended if...else if...else if...else structure, when the conditions would consist of comparing various values against a single value, you can use the switch statement; however, I never use it, and I don’t recommend that you do either, as it is rather confusing and can easily go wrong. See K&R 3.4 if you’re interested.

The C for loop needs some elaboration for beginners (Example 1-1). The before-all statement is executed once as the for loop is first encountered and is usually used for initialization of the counter. The condition is then tested, and if true, the block is executed; the condition is usually used to test whether the counter has reached its limit. The after-each statement is then executed, and is usually used to increment or decrement the counter; the condition is then immediately tested again. Thus, to execute a block using integer values 1, 2, 3, 4, and 5 for i, the notation is:

int i;
for (i = 1; i < 6; i++) {
    // ... statements ...
}

The need for a counter intended to exist solely within the for loop is so common that C99 permits the declaration of the counter as part of the before-all statement; the declared variable’s scope is then inside the curly braces:

for (int i = 1; i < 6; i++) {
    // ... statements ...
}

The for loop is one of the few areas in which Objective-C extends C’s flow-control syntax. Certain Objective-C objects represent enumerable collections of other objects; “enumerable” basically means that you can cycle through the collection, and cycling through a collection is called enumerating the collection. To make enumerating easy, Objective-C provides a for...in operator, which works like a for loop:

SomeType* oneItem;
for (oneItem in myCollection) {
    // ... statements ....
}

On each pass through the loop, the variable oneItem (or whatever you call it) takes on the next value from within the collection. As with the C99 for loop, oneItem can be declared in the for statement, limiting its scope to the curly braces:

for (SomeType* oneItem in myCollection) {
    // ... statements ....
}

To abort a loop from inside the curly braces, use the break statement. To abort the current iteration from within the curly braces and proceed to the next iteration, use the continue statement. In the case of while and do, continue means to perform immediately the conditional test; in the case of a for loop, continue means to perform immediately the after-each statement and then the conditional test.

C also has a goto statement that allows you to jump to a named (labeled) line in your code (K&R 3.8); even though goto is notoriously “considered harmful,” there are situations in which it is pretty much necessary, especially because C’s flow control is otherwise so primitive.

Note

It is permissible for a C statement to be compounded of multiple statements, separated by commas, to be executed sequentially. The last of the multiple statements is the value of the compound statement as a whole. This construct, for instance, lets you perform some secondary action before each test of a condition or perform more than one after-each action (an example appears in Chapter 17).

We can now turn to the question of what a condition consists of. C has no separate boolean type; a condition either evaluates to 0, in which case it is considered false, or it doesn’t, in which case it is true. Comparisons are performed using the equality and relational operators (K&R 2.6); for example, == compares for equality, and < compares for whether the first operand is less than the second. Logical expressions can be combined using the logical-and operator (&&) and the logical-or operator (||); using these along with parentheses and the not operator (!) you can form complex conditions. Evaluation of logical-and and logical-or expressions is short-circuited, meaning that if the left condition settles the question, the right condition is never even evaluated.

Warning

Don’t confuse the logical-and operator (&&) and the logical-or operator (||) with the bitwise-and operator (&) and the bitwise-or operator (|) discussed earlier. Writing & when you mean && (or vice versa) can result in surprising behavior.

The operator for testing basic equality, ==, is not a simple equal sign; forgetting the difference is a common novice mistake. The problem is that such code is legal: simple assignment, which is what the equal sign means, has a value, and any value is legal in a condition. So consider this piece of (nonsense) code:

int i = 0;
while (i = 1) {
    i = 0;
}

You might think that the while condition tests whether i is 1. You might then think: i is 0, so the while body will never be performed. Right? Wrong. The while condition does not test whether i is 1; it assigns 1 to i. The value of that assignment is also 1, so the condition evaluates to 1, which means true. So the while body is performed. Moreover, even though the while body assigns 0 to i, the condition is then evaluated again and assigns 1 to i a second time, which means true yet again. And so on, forever; we’ve written an endless loop, and the program will hang. (And, depending on what compiler and settings you’re using, you might not even get a warning of trouble ahead.)

C programmers actually revel in the fact that testing for zero and testing for false are the same thing and use it to create compact conditional expressions, which are considered elegant and idiomatic. I don’t recommend that you make use of such idioms, as they can be confusing, but I must admit that even I do occasionally resort to this sort of thing:

NSString* s = nil;
// ...
if (s) {
    // ...
}

The idea of that code is to test whether the NSString object s, between the time it was declared and the start of the if-block, has been set to an actual string. Because nil is a form of 0, the condition is asking whether s is non-nil. Some Objective-C programmers would take me to task for this style of writing code; if I want to test whether s is nil, they would say, I should test it explicitly:

if (s == nil)

In fact, some would say, it is even better to write the terms of the comparison in the opposite order:

if (nil == s)

Why? Because if I were to omit accidentally the second equal sign, thus turning the equality comparison into an assignment, the first expression would compile (and misbehave, because I am now assigning nil to s), but the second expression would certainly be caught by the compiler as an error, because assigning a value to nil is illegal.

Objective-C introduces a BOOL type, which you should use if you need to capture or maintain a condition’s value as a variable, along with constants YES and NO (actually representing 1 and 0), which you should use when setting a boolean value. Don’t compare anything against a BOOL, not even YES or NO, because a value like 2 is true in a condition but is not equal to YES or NO. Just use the BOOL directly as a condition, or as part of a complex condition, and all will be well. For example:

BOOL snil = (nil == s);
// ...
if (snil) // ... not: if (snil == YES)

Get Programming iOS 4 now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.