Physics and Astronomy |
Back to top
On this page
Contents Conditionals: if() and switch()Control statementsOur very first example of an algorithm, a set of directions from one place to another, was just a sequence of steps to be followed ( or "executed") from beginning to end. But very few algorithms are like that, and our second example, calculating wages, contained two new features:
Another point worth noting is that these were nested one inside the other (the "which day is it?" condition was inside the "for each day" loop which was in turn inside the "for every employee" loop. Just about all real algorithms contain these features and over the next two lectures we shall look at the C statements most commonly used for this. In this lecture we will study the conditional statements if() and switch() which use logical conditions to control whether code is executed. In the next lecture we shall introduce loops which execute code zero or more times. Like everything else in C, these statements operate in a simple and consistent way, the key is in how we use them to create more-complicated programs. Control statements control which instruction is executed next. The if() statementThe if statement is the primary way for us to specify that a set of instructions ("statements") are only to be executed if a certain condition is true. Any statement can have the construction if (expression) put in front of it. if ( x > 0 ) printf("x is greater than zero\n"); The sub-statement following the "if()" is only executed if the controlling expression is true. It is conventional to put the sub-statement on a separate line indented from the original: if ( x > 0 ) printf("x is greater than zero\n"); There is no semi-colon after the if() as it is all one statement. This is very important: x = -1.4; if ( x > 0 ); // Subtle but horrible mistake printf("x is greater than zero\n"); In this buggy code the if() statement terminates with the semicolon. Therefore the printf() will always occur. (Notice that the indentation has no effect, it's just there to make it easier for us to understand.) The meaning of > should be fairly obvious, C also has < (less than), >= (greater than or equal to: notice no space) and <= (less than or equal to), as well other operators which we discuss below.
elseThe word else has the meaning of "otherwise". Here we use the "less than" operator for variety: if ( x < 0 ) printf("x is less than zero\n"); else printf("x is greater than or equal to than zero\n");
else if ()More complicated logical conditions can be expressed using else if () which introduces an alternative that is only examined if the first if() was false. It occurs before the else, if any. if ( x > 100 ) printf("x is greater than 100\n"); else if ( x > 10 ) printf("x is greater than 10 but less then or equal to 100\n"); else if ( x > 0 ) printf("x is greater than 0 but less then or equal to 10\n"); else printf("x is negative\n"); Note the orderif(first condition) do something; else if(second condition) // Only if first condition was false do something else; else if(third condition) // Only if second condition was also false do something else again; else // Only if third condition was also false do something else yet again; // The program look at each possibility in turn: if it finds one that is true it jumps to the end, otherwise it executes the contents of the else The expression else if () introduces an alternative that is only examined if the first if() was false.
There are two things to notice:
There can be as many else if ()s as we like, they are examined in order but once any one of them evaluates to "true" (non-zero) the rest are ignored, as is the else if there is one. Technically the entire if() ... else construct forms a single compound statement. Reminder
if() ... else if() v several if()sThe previous example was a single compound if() statement where only one possible action could occur. In the following we have replaced all the "else if()"s with "if()" to create several independent if() statements where more than one action could occur: if ( x > 100 ) printf("x is greater than 100\n"); if ( x > 10 ) printf("x is greater than 10 but less then or equal to 100\n"); if ( x > 0 ) printf("x is greater than 0 but less then or equal to 10\n"); else printf("x is zero or negative\n"); Step through the above two examples and make sure you understand the difference.
Relational operatorsWhat is truth?C does not use a separate "logical" or Boolean (true/false) class for its conditional expressions. Instead, the expression in an if statement is simply an arithmetic expression which is taken to be "true" if it is non-zero. Thus, in principle we can write: if ( -0.001 ), which would always be considered to be "true" as -0.001 is not equal to zero. Needless to say, this wouldn't be a very good idea! The following relational operators all return the (int) values one ("true") or zero ("false").
An operand is a fancy name for the things operators operate on, e.g. in the expression "x * y" the operator is * and the operands x and y. As with the arithmetic operators, C "does the right thing" if the operands are of different type, e.g. if (0.001 > 0) behaves as we would expect ("true"), it doesn't get confused by the fact that one side is a double and the other an integer. Note that the equality operator is a double equals sign, a single equals sign is the assignment operator (see below). There is no space in ==, !=, <= or >= . Other "logical" operatorsC also defines the following operators for the logical operations "AND", "OR", etc. These operators all have scalar operands, usually individual relational expressions such as x > 0, etc. In the following examples we use constants to make clear what the results should be although in practice some or all of them would be variables, for obvious reasons.
Note that the OR operator "||" evaluates to one ("true") if either or both of its operands are non-zero. The "and" and "or" operators && and || combine other expressions. Parentheses "()" group other expressions and ! is negation.. Example: Game of LifeConway's classic Game of Life considers a rectangular grid of cells, each of which is either alive or dead. Each cell has eight neighbours; the diagram below shows a live central cell with four live neighbours and four dead ones:
Put another way: a cell with exactly three live neighbours will be alive in the next generation, as will a cell that is alive in this generation with exactly two live neighbours. All other cells will be dead. If we denote a live cell by the value one and a dead cell by zero, then an if statement to calculate the next-generation state of a cell would be: We don't actually need the parentheses after the "||" but there's less chance of us making an error if we put them in. if ( neighbours == 3 || ( state == 1 && neighbours == 2 ) ) nextstate = 1; else nextstate = 0; Semi-advanced point: && and || evaluate their arguments in orderNormally when we write an expression such as "x + y" the compiler may choose to evaluate the two arguments in whatever order it likes. But && and || are different: the computer will always evaluate the left-hand side first. Why? Because for || if the left-hand side is true it doesn't need to evaluate the right-hand side to know the overall value is one (true) and therefore it won't. A logical sequence such as: expression1 || expression2 || expression3 || ... can be thought of a meaning "evaluate expression1, expression2 etc. until one is true (non-zero), and return one. If none of them is true the overall answer is zero". Similarly: expression1 && expression2 && expression3 && ... can be thought of a meaning "evaluate expression1, expression2 etc. until one is zero (false), and return zero. If all of them are non-zero the overall answer is one". In either case unnecessary expressions are not evaluated. The "and" and "or" operators && and || only evaulate their Right-Hand-Sides when necessary. Precedence"And" binds more closely than "or" ie a && b || c is equivalent to (a && b) || c. However, the "extra warnings" option to our compiler will insist we explicity put in the parentheses. a && b || c is equivalent to (a && b) || c, but the "extra warnings" option to our compiler requires the parentheses. Example of combining expressions with && and ||Here we use expressions involving variables rather than constants, which of course is what we would do in practice: // Example of using if() with && and || #include <stdio.h> int main() { int j = 2, k = 3; double x = 4.1; // LHS is true so RHS is NOT evaluated if ( j + k > x || x > 10 ) printf("Hello 1\n"); // LHS is true so RHS IS evaluated (but is false) if ( j + k > x && x > 10 ) printf("Hello 2\n"); // Combining && and || if ( (j + k < x && x > 10) || j < k ) printf("Hello 3\n"); return 0; } Three common mistakes to look out forThe compiler will warn you about all the errors below with the (PCs running Code::Blocks will flag them as an error, XCode on the Mac will show the Yellow Triangle of Peril\x99), although the warning may be a little opaque, for example words to the effect of "use parentheses if you really want to do this". 1. The grandmother of all bugs - missing out an = signThe equality operator is the double equals sign ==, and the assignment operator single equals sign = .This leads to the classic bug: if ( mass = 0 ) // Wrong!
printf("The mass should not be zero!\n");
Use if ( mass == 0 ) not ( mass = 0 ). 2. && and || are double tooLess common is to have a single & or | when we meant to have two:if ( j > 0 & j % 2 == 0) // Wrong printf("j is positive and even\n"); This is also legal (but almost certainly wrong) as a single & is the "bitwise and" operator which we won't be using until a later lecture. && and || are double just like == 3. a == b == c doesn't do what we might expectUse: a == b && b == c instead. This is because C, which places great value
on consistency, treats the expression a == b == c
in the same way as it treats a * b * c.
First it evaluates a == b to obtain either
zero or one, then it checks that c is equal to that
value (zero or one). The expression is therefore equivalent
to:
( a == b && c == 1 ) || (a != b && c == 0 ) which is probably not what you want! Similarly, use a > b && b > c instead of: a > b > c // Wrong Example// Demonstrate classic error with if() operators // This code will trigger advanced error warnigs if enabled int main() { int a = 2, b = 2, c = 2; if ( a == b == c ) // Wrong! printf("They are all the same\n"); if ( a == b && b == c ) // Right printf("They are all the same\n"); if ( a < b < c ) // Wrong! printf("a < b < c\n"); if ( a < b && b < c ) // Right printf("a < b < c\n"); return 0; } use if (a == b && b == c) not (a == b == c) { ... } blocksOne useful feature of C is that we can put a { ... } block wherever we can put a single statement. There is no semi-colon after the closing }, just like there isn't one after the { ... } block that forms the main() function. So, we could have chosen to write the above as: if ( x > 0 ) { printf("x is greater than zero\n"); } else { printf("x is less than or equal to zero\n"); } Whilst it's not necessary in this case, don't hesitate to use { } if you think it's clearer. Remember the megaprinciple! We can, of course, put more than one statement in a { ... } block, which is the main reason for having them. Let's practice using the "greater than or equal to" operator: if ( x >= 0 ) { printf("x is greater or equal to zero\n"); y = sqrt(x); } else { printf("x is less than zero\n"); } However, the following would be a disaster: if ( x >= 0 )
printf("x is greater than zero\n");
y = sqrt(x); // ERROR
Turn to your neighbour and ask why this
is.
We can put a { ... } block wherever we can put a single statement. There is no semi-colon after the closing }. Any { ... } block can have its own variablesWe have already seen that the main() { ... } block can have variables declared inside of it. C being consistent, so can any { ... } block. These variables are only usable within the block and their values are not preserved if the block is re-entered, for example if it is a loop. Any { ... } block can have its own variables whose values are not preserved if the block is re-entered. Indentation and layoutIn the "Introduction to C" lesson we briefly touched upon the fact that clarity of our code is enormously helped by indentation. Compare a set of bullet points printed twice, with and without indentation: :
The use of so-called white space, including blank lines, to visually indicate structure has been used in books for centuries. Our code should be indented in the same way as a list of bullet points and for the same reason; to indicate its structure. If we look at our example programs we will see that all the lines start flush at the left except for the contents of the { ... } block which are indented by the same amount. In this case it's two spaces, four spaces is also common. The closing } is brought back to the left, level with the code outside of the { ... } block. When we write functions containing loops and conditionals ("if statements"), their contents will also be indented by the same amount, thus making the logical structure of our function clear at a glace. We also left a couple of blank lines before the start of the main() function and this is also good practice for all functions. Minor sub-divisions within a function can be denoted by a single blank line. The google style guide specifies that { ... }
blocks are indented by two spaces
and that lines are a maximum of eighty
characters long. This is extremely important and teams of programmers have instructions ("style guides") that specify exactly how code should be formatted. In general C doesn't care about white-space at all outside of character strings, although the compiler will get upset if we try to break up words ("pri ntf") or numbers ("1.2 34"). Also remember that we will sometimes need to print our program, so follow the google guidelines and limit our lines to eighty characters. We can break long statements over several lines, making sure that the continuation lines are themselves properly indented. We are also allowed to have more than one statement on the same line but again this is highly disapproved-of. Layout summary
Correct layout of our code, particularly the indentation and blank lines, makes it much easier to understand. The markers will be instructed to be extremely strict on this as it's very important and not hard to get right. Nested if()sWe can put if() statements inside each other. This is useful when we have more than one criterion to choose between, as in the following example. ExampleIn this example we wish to do different things according to whether a number ("j") is positive or negative and also whether it is even or odd: The {}s are unnecessary here but again I think they make the code look much clearer - remember the mega-principle! if ( j > 0 ) { if ( j % 2 == 0 ) printf("j is even and greater than zero\n"); else printf("j is odd and greater than zero\n"); } else { // j <= 0 if ( j % 2 == 0 ) printf("j is even and negative\n"); else printf("j is odd and negative\n"); } Armed with "else if()" we could rewrite this as follows: if ( j > 0 && j % 2 == 0 ) printf("j is even and greater than zero\n"); else if ( j > 0 && j % 2 != 0 ) printf("j is odd and greater than zero\n"); else if ( j <= 0 && j % 2 == 0 ) printf("j is even and negative\n"); else if ( j <= 0 && j % 2 != 0 ) printf("j is odd and negative\n"); else printf("I seem to have made a mistake!\n"); One minute discussion: turn to your neighbour and discuss which version of the even/odd, positive/negative code is clearer. The switch() statementThe switch() statement chooses one of several choices, with an optional default, based on an integer controlling expression. A situation that can occur quite often is the need to choose between one of several integer values and take the appropriate action for each. This could be done with a large if() statement but it is rather clumsy: // This works but is a bit clumsy int number; // Assign a value to number... if ( number == value1 ) { // ... Do something } else if ( number == value2 ) { // ... Do something else again } else if ( number == value3 || number == value4 ) { // ... Do something else yet again } else { // ... Do yet another thing } C provides a special statement for dealing with this situation, the switch() statement, subject to two conditions: The expression that controls the switch() choice must be an integer. The options must be integer constants and must all be different. The outside of a switch() statement has the same familiar form as an if() or while() statement: switch (integer-expression) { ... } The case labelsThe { ... } block contains the options to be chosen (switched) between. They have the general form: case integer-constant: statements; The choices ("labels") are followed by colons: case 2: It's conventional to "pull back" the labels to align them with the switch() statement itself. Within the switch() statement, the program jumps to the choice (technically referred to as a label) that matches the value of the expression. The switch() statement jumps to the case whose value equals that of the controlling expression, skipping over any intervening code. Example: a simple menuA common example of this is printing a simple menu with choices number 1, 2, 3 etc. Useful tip: A common problem with menus is that the string of options can be extremely long. C solves this for us by joining strings separated by spaces, without commas, into one large string. We illustrate this below. C joins several strings separated by spaces, without commas, into one large string. Reminder: '\t' is the tab character. // A simple menu with a switch()
printf("\n Menu\n\n"
"\t 1. Some option\n"
"\t 2. Another option\n"
"\t 3. A third option\n\n"
"\t 0. Quit\n");
scanf("%d", &option);
switch (option) {
case 0:
printf("Goodbye, thanks for using the switch program\n");
return 0;
case 1:
printf("You chose option one.\n");
break;
case 2:
printf("You chose option two.\n");
break;
case 3:
printf("You chose option three.\n");
break;
default:
printf("Unknown option: %d\n", option);
}
The default labelRather like the else of an if() statement, the default label applies if none of the other values match. Just like with else it is optional. The default label can occur anywhere inside the switch(), but for obvious reasons it is usual to put it at the end. If there is no default label and none of the other labels match, the program jumps over the entire { ... } block. If no case matches then control jumps to the default: label, if there is no default label the program skips over the entire { ... } block. Falling through the cases
|