MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Go with the Flow

by Seth Roby
08/19/2003

In the first installment of C is for Cocoa, we moved from one line of code to two lines of code, pointing out that the second one would be executed directly after the first, like items being crossed off of a to-do list. Our program knows to do the things we tell it to do in the order that we tell it. In lesson 2, we learned how to tweak this simple list approach by making single lines of code call functions where many more lines of code might be found. Our program knows that some of the things we tell it require more than one step.

Although we didn't make note of it at the time, both of these demonstrate the same concept, which is the idea that we'll be tackling today, usually called the flow of control. In our first program, as each line of code was found in order, that line was where the code program "was", and it got executed before the program would move on to the next line. Once we added functions to the mix, the control could move "into" those, and again would take the code on one line at a time, making sure to execute every line of code before it was done.

Related Reading

Learning Cocoa with Objective-C
By James Duncan Davidson, Apple Computer, Inc.

But sometimes we don't want it to execute every line of code. Sometimes we want it to execute this line of code when the computer is connected to a network, and this line if it's not. Sometimes we want the computer to choose from a whole range of things to do, depending on one circumstance. Sometimes we want the program to do something a whole lot of times, like while the user is idle, or as many times as there were key presses.

In all of these cases, we want to change the flow of control in our program. We want the computer to do something that's not just a simple list of directions, each to be done once and checked off, never to be looked at again. We want our program to do what makes sense given its circumstances, and not bother with the rest. This is what we'll be looking at in this lesson.

Learning How to Say No

But before we do that, we need to learn how to say no. When you ask a simple question, you expect a simple answer. But as we learned in lesson 1, you can't ask the computer a question, and your code certainly won't ask you any. Your program might ask the user questions, but the programmer is left in the position of uttering only imperatives forever and ever. The solution to all of this is borrowed from an old Irish schoolteacher named George Boole, who invented a way to express "yes" and "no" called boolean logic. It is traditional in C to refer to these as TRUE and FALSE (note the case: you must use all caps or it won't work), but Cocoa traditionally uses YES and NO. In this lesson, we'll use the C tradition because they stand out more in prose.

Now you might think that the best way to do this would be to make a boolean type that handles TRUE/FALSE distinctions. And indeed in a perfect world that's exactly how this would be handled. In newer languages, that's what's done. In C, however, we cheat a little. Instead of actually making a type that handles this, we use a type we already have and pretend. So we take our familiar int and whenever it's a zero, we read that as FALSE; when it's not, we read that as TRUE. This might seem a little odd, but it actually leads to a number of simplifications later on, which we'll see in later lessons. But now let's head into this lesson and learn to control our flow.

If You Code It, It Will Run

The simplest type of flow control structure is the if block. It's so simple that you probably don't need much help dissecting the following code block:

int integerForSeedValue(int seedNumber) {
	int retval = 0;

	if (1) {
		printf("This is always true.\n");
		retval = seedNumber - 3;
	}
	
	return retval;
}

You should use this and later examples in your code to modify the integerForSeedValue function we wrote in the last lesson. Each of the following code blocks will work and do something slightly different. As we discuss each, run your program with the newest code.

I've added a declaration for the variable retval, which is the value that the function will return (this is a common name for such a variable). Also note that the variable is returned at the end of the function. The middle is where the new conditional is.

Here we've added if, and then something in parentheses, and then a block. The key to it all is that something in the middle, in parentheses. That is called our condition, and if it's TRUE, the code in the block gets executed, and the function returns seedNumber -3. If it's not, then the computer skips the block entirely, and the function returns the 0 that retval is set to in its declaration. Since our condition here is the constant 1, the block is always executed. Let's make the code a little more interesting:

int integerForSeedValue(int seedNumber) {
	int retval = 0;
	int cond = 17 < 23;

	if (cond) {
		printf("What do you know: 17 *is* less than 23!\n");
		retval = seedNumber - 3;
	}
	
	return retval;
}

We added a line of code that seems familiar to us: it's a variable declaration that creates a variable named cond. But how it does that is a little new. Instead of the familiar arithmetic operators, we have the comparison operator "<", the less-than operator, which you remember from grade school when you were learning which numbers were bigger than others. Like all operators, it operates on the things around it. If it was an addition sign, it would add the two things around it, but since it's the less-than operator, it compares them. If the thing on the left is less than the thing on the right, it evaluates to a boolean value for TRUE. Since 17 is always less than 23, the first line sets cond to TRUE. Then, when we get to our if condition, since cond is true, the block is executed. If you switched the operator to be the greater-than operator ">", cond would be FALSE and the if block would not be executed.

What would be executed in that case is the optional else block, which we can see in this code example:

int integerForSeedValue(int seedNumber) {
	int retval = 0;
	int cond = 17 > 23;	//note we're now using the greater-than operator

	if (cond) {
		printf("What do you know: 17 *is* greater than 23!\n");
		retval = seedNumber - 3;
	} else {
		printf("Well lookie here: 17 is *less than* 23!\n");
		retval = seedNumber * 3;
	}
	
	return retval;
}

The else block will be executed whenever the if block is not executed--and vice versa. But note that neither of these need to be a block at all. You can just put a single line of code after the if's condition or the else, and that line of code will be executed in lieu of a block:

int integerForSeedValue(int seedNumber) {
	int retval = 0;
	int cond = 17 > 23;
	
	//no printf's here; only one line statements
	if (cond) retval = seedNumber - 3;
	else retval = seedNumber * 3;
	
	return retval;
}

It's a good practice to always include the block, because leaving it out can lead to headaches later on if you want to add code to either side of the conditional and you forget to add the brackets then. You're usually better off making a habit of always putting the brackets in whether you need them right away or not.

But it is often the case that the logic you need is more complex than a simple this or that choice. It's often this, or that, or this other thing. In that case, we chain our if/else statements together to allow more and more decisions, like this:

int integerForSeedValue(int seedNumber) {
	int retval = 0;
	int cond = 17 > 23;

	if (cond) {
		printf("Amazing! 17 is *greater than* 23! My life has changed\n");
		retval = seedNumber - 3;
	} else if ((seedNumber < 5) && (seedNumber > -5)) {
		printf("That there seed number is real close to zero!\n");
		retval = seedNumber * 2;
	} else {
		printf("No special cases here; do what we normally do!\n");
		return = seedNumber * 3;
	}
	
	return retval;
}

The second if statement is only tested if the first fails, which it always will since 17 is never greater than 23. But here we also do two important new things. First, our condition includes the function's input. This, as you can imagine, is much more useful than defining variables that will always be TRUE or FALSE as we have done before. Secondly, we did some craziness with some ampersands that's totally new.

What we're doing here is making a compound condition. Now, instead of just checking one condition, we're checking two: we want to make sure seedNumber is less than 5 and greater than -5. That double-ampersand means boolean and. Why not a single ampersand? That's used for something else, which you won't need to care about for some time.

Go back over the examples of if/else blocks and try them in your program; each one works a little differently, so try each one if you haven't yet. In each example, watch what code does and does not get executed to see the flow of control. When you're done, we'll look at a different way to do the same type of things.

Now We're Writing in Code

Some people are all about shortening the code they write. This can be an admirable goal: less code can mean a faster operation and a simpler more maintainable codebase. Sometimes brevity can make your code much cleaner. Toward these goals C includes an operator that performs one of the tasks that an if/else block is often called in to perform: setting a variable to two different values based on some boolean logic. This operator, called the ternary operator, turns this code:

int integerForSeedValue(int seedNumber) {
	int retval = 0;
	
	if (seedNumber == 8) {
		retval = seedNumber - 3;
	} else {
		retval = seedNumber * 3;
	}
	
	return retval;
}

Into this code:

int integerForSeedValue(int seedNumber) {
	return (seedNumber == 8) ? (seedNumber - 3) : (seedNumber * 3);
}

That's quite a savings of space, but the second snippet looks like quantum physics. Let's take this line apart token by token.

We start with return, which we know and love from our last lesson. Then we've got a bit in parentheses that is boolean logic. This is the condition we're used to in a normal if/else block, expressed in exactly the same way. If we put a semicolon here, the result of the condition would be returned. Instead we have a question mark, which tells the computer that it just read a condition for a ternary operator, and the other parts are next. The next bit in parentheses is what we want the ternary operator to evaluate to if the condition is true. This is first, just like the true condition is first in a normal if/else block. Then we have a colon, which tells the computer that we're done with the true case and moving on to the false case. Here we put what we want the ternary operator to evaluate to if the condition is false.

So you can read the question mark as "if the following is true, evaluate this" and the colon as "but if the condition was false, evaluate this instead." But note that you can only evaluate the cases, meaning multiple lines isn't an option, and each case must evaluate to be the proper type or you'll get a warning.

Another thing to note is that we've introduced a new operator, "==", the equality conditional. This checks to see if the thing on the left is equal to the thing on the right, and if they are it evaluates to TRUE, otherwise to FALSE. Why isn't it a single equals sign? Because we already use that token to set the value of variables and that means we can't use it here.

Flip the Switch

When you're dealing with code that must decide between a few options, if is the way to go. But if you need to use one input to decide amongst a lot of things, Then it's much simpler to just use a switch:

int integerForSeedValue(int seedNumber) {
	int retval = 0;
	
	switch (seedNumber) {
		case 1:
			printf("The Seed was one, not many.\n");
			retval = seedNumber;
			break;
		case 8:
			printf("We have an eightfold seed.\n");
			retval = seedNumber * 2;
			break;
	}
	
	return retval;
}

In this section, too, we will examine many different incarnations of the integerForSeedValue function, and you should run your program with each of them.

Let's pull the code apart. We start with the token switch, then something in parentheses. After that is a block. But the stuff in parentheses isn't a condition, it's the switch variable. This is the variable whose value determines what gets done inside the switch block.

Once we know this variable, we move through the switch block looking for case lines with the same value, and ignoring all the other code until we find one. When we do find one, we execute the code after it until we hit a break statement, which pops us out of the block. Note that hitting another case statement will not end the block: a break is the only thing that will stop it. That seems odd until you consider the following:

int integerForSeedValue(int seedNumber) {
	int retval = 0;
	
	switch (seedNumber) {
		case 1:
			printf("Surreptitiously, a singular seed..\n");
			retval = seedNumber;
			break;
		case 6:
		case 7:
		case 8:
			printf("Our seed is 6, 7, or maybe even 8!\n");
			retval = seedNumber * 2;
			break;
	}
	
	return retval;
}

Which means that if seedNumber is 6, 7, or 8, retval will be set to seedNumber * 2. In other words, you can group cases together if you want them all to do the same code.

But note that each case here is a constant integer. This is a very important limitation in the way switch works: its cases can only be constants and can't be boolean expressions, like if/else blocks' conditions.

The switch syntax also provides a way to catch anything you didn't account for. The following snippet is an example:

int integerForSeedValue(int seedNumber) {
	int retval = 0;
	
	switch (seedNumber) {
		case 1:
			printf("He is The One.\n");
			retval = seedNumber;
			break;
		case 8:
			printf("And the number of the seed shall be eight.\n");
			retval = seedNumber * 2;
		default:
			printf("What a seed it is!\n");
			retval++;
	}
	
	return retval;
}

We added a default case. The code here will be executed if no matching case is found, or if no break is found beforehand. Note that we took away the break in case 8. This allows the default to be executed when seedNumber is 8, which it is in our program as written in Lesson 2.

But you might have noticed that the code in our default case is something we haven't seen before. The "++" is yet another operator, the increment operator. It takes whatever is in retval and adds one. It's sister the decrement operator, "--", subtracts one.

Run all the different switch examples and see the results. Try to trace where the control flows.

A Conditional Ending

We've learned three ways of changing how our program control flows, each of which is useful for different situations. We can now change code execution based on boolean logic, we can set variables based on the same logic, and our code can change how it works based on the value of variables.

But there's more. As useful as these structures are, they don't let you do the same thing over and over, which is the exclusive domain of loops, which we will cover in our next lesson.

Seth Roby graduated in May of 2003 with a double major in English and Computer Science, the Macintosh part of a three-person Macintosh, Linux, and Windows graduating triumvirate.


Return to MacDevCenter.com

Copyright © 2009 O'Reilly Media, Inc.