Skip to content
Physics and Astronomy
Home Our Teaching Resources C programming Conditional statements
Back to top
On this page
Contents

Conditionals: if() and switch()

Control statements

Our 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:

  • loops, where steps were repeated several times
  • if conditions (more formally knowns as conditionals) where certain steps were taken only a particular condition was met and, optionally, other steps which were taken if it was not.

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() statement

The 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.

  1. A simple if() statement
  2. To practice using the if() statement
  3. Create a new program in the on-line compiler with a suitable title and initial comment
  4. Declare two integers and read in their values using scanf() and %d
    • Whenever we read in data from the keyboard we should always print a message beforehand. In this case it could be as simple as:
      printf("Pease enter two integers\n");
  5. Write an if() statement so that if the first number is greater than the second your program prints "the first number is greater than the second" and otherwise does nothing.
  6. Obviously you should check this for various numbers you enter.

else

The 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");
  1. else
  2. To practice using the else statement
  3. Modify your previous program so that if the first number is not greater than the second it prints a suitable message (using the else statement). That is, it will always print out one message or the other but never both.

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");
Step through this code


Note the order

if(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.

  1. Step through the above example.
  2. To understand the else if
  3. Step through the above example in a new window.
  4. Step through the code making sure you always know what the code will do next.
  5. At the end of the code the parts that have been executed will be highlighted. Make sure you understand the reasons why some parts were executed and others were not.

There are two things to notice:

  1. There is a space in "else if".
  2. Had we missed out the else s then several printf()s would have executed, which obviously would have been an error.

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

  • There is no semi-colon after the if ( ... ). This would end the if statement at that point.
  • There is also no semi-colon after the { ... } block.
  • The contents of the block should always be indented for clarity but the compiler takes no notice of that: it always folows the syntax.
  1. else if()
  2. To practice using the else if() statement
  3. You may find that the easiest way to do steps 1 and 2 is to copy and paste your initial if() and printf() statements so that you know have two of them:
    if()
      printf()
    if()
     printf())
    and to edit the result.
     
  4. Modify your previous program so that the initial if() condition tests to see if the first number is greater than twice the second.
  5. Add an else if() statement that performs the original test: (first number is greater than the second). Remember the order:
    if()
    else if()
    else
  6. Modify the first two printf() statements so that their messages now make sense.
  7. Check your program works properly for all three cases.

if() ... else if() v several if()s

The 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 action could occur:

  if ( a == b) {
    c = c; // Do nothing
  }
  else {
    c = c/(a-b);
  }

Step through the above two examples and make sure you understand the difference.

  1. Mistakes with if()
  2. To show the effects of ordering and the difference between if() ... else if() and if() ... if()
  3. Change the title and initial comment of your if()program to indicate it will contain some deliberate mistakes.
  4. Now swap over the two tests so that the if() checks to see if first number is greater than the second and the else if() checks to see if first number is greater than twice the second.
  5. Again, check your program for all three possible cases.
    • Does it work properly?
    • Why (not)?
    If you are not sure what's going on step through the previous example code again.
  6. Remove both elses so that both just read if(), rather like the example above. Try all three classes of values and see what happens.

Relational operators

What 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").

Operator One if
expression1 > expression2 expression1 is greater than expression2
expression1 < expression2 expression1 is less than expression2
expression1 >= expression2 expression1 is greater than or equal to expression2
expression1 <= expression2 expression1 is less than or equal to expression2
expression1 == expression2 expression1 is equal to expression2
expression1 != expression2 expression1 is not equal to expression2

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" operators

C 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.

Operator Meaning   Example & result   Example & result
&& AND 8 > 1 && 7 < 4 0 8 > 1 && 4 < 7 1
|| OR 8 > 1 || 7 < 4 1 8 > 1 || 4 < 7 1
( ) Parentheses ( 6 > 4 || 4 < 6 ) && 6 == 3 0 6 > 4 || ( 4 < 6 && 6 == 3 ) 1
! Negation ! 1 0 ! ( 8 > 1 && 4 < 7 ) 0

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 Life

Conway'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:

 * 
 * 
 *   * 

 * 
The game progresses in generations, in each generation a live cell with either two or three live neighbours stays alive, otherwise it dies. A dead cell with exactly three live neighbours becomes alive, otherwise it stays dead. In the above example the central cell would die.

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 order

Normally 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;
}
Step through this code


Three common mistakes to look out for

The 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 = sign

The 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");
 

Step through this code

  1. Step through the above example.
  2. To see the difference between = and == inside an if()
  3. Step through the above example in a new window.
  4. Step through and look very carefully as it evaluates mass = 0 inside the if(). Notice how it behaves exactly the same as it does outside of an if().
  5. Now look what happens when it evaluates the correct expression mass == 0.
This has two consequences:
  1. The variable mass is assigned the value zero.
  2. This value of the variable mass is used as the condition of the if() which is of course false, as mass has just been set to zero.

Use if ( mass == 0 ) not ( mass = 0 ).

2. && and || are double too

Less 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 expect

Use:

  a == b && b == c

instead.

This 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;
}
Step through this code

 

use if (a == b && b == c) not (a == b == c)

{ ... } blocks

One 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 than 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
Step through this code


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 variables

We 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 layout

In 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: :

Pets: pros and cons

  • Cat
    • Easy to look after
    • Kills birds
  • Dog
    • Friendly
    • Needs daily walk
  • Great white shark
    • Looks great.
    • Feeds itself
    • Has a nasty nip:
      • Big pointy teeth
      • Can open jaws really wide
    • Not ideal first pet

Pets: pros and cons

  • Cat
    • Easy to look after
    • Kills birds
  • Dog
    • Friendly
    • Needs daily walk
  • Great white shark
    • Looks great.
    • Feeds itself
    • Has a nasty nip:
      • Big pointy teeth
      • Can open jaws really wide
    • Not ideal first pet

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

  • Indent the contents of { ... } blocks by either two or four spaces, always using the same amount.
  • Bring the final } back in line with the outside of the block.
  • Leave two or three blank lines between functions.
  • Leave a single blank line within a function when we wish to emphasise internal divisions. Note the logic here: gaps between lines within functions are less than those between functions so that a quick glance tells the brain the code is divided into divsions (in this case functions) which in turn have (informal) subdivisions.

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()s

We 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.

Example

In 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() statement

The 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 possibilities 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 labels

The { ... } 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 menu

A 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);      
  }
Step through this code


The default label

Rather 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

The program does not jump out of the switch() statement when it meets the next label.

The break statement breaks out of the switch() to the statement following the switch() statement's { ... } block.

  1. Step through the above example.
  2. to see what happens if we miss out the break statement
  3. Step through a version of the above menu code where we have "accidentally" missed out the break statement.
  4. Notice how the code executes two printf() statements, not just the one and how it just skips over the "case: 2" statement.

A common use of this is to assign the same action to two or more cases. We shall see some examples of this below.

There is no need for for a break statement after label 0 as the return returns from the whole function so label 1 would never be reached if option had had the value zero.

A common bug is to forget the break statement. If you deliberately omit the break statement I suggest putting in a comment to indicate this, except in the case where one label immediately follows another. An example of this is shown next week.

Example: multiple labels with the same action

Consider a test if a prime number less than 10. In this case either of the numbers 2, 3, 5 and 7 gives rise to the same action.

// Demonstrate switch() consecutive labels
#include <stdio.h>

int main() {
  int num = 7;

  // Is num a prime less than 10?
  switch(num) {
  case 2: case 3: case 5: case 7:
    printf("%d is a prime number less than 10\n", num);
    break;

  default:
    printf("%d is not a prime number less than 10\n", num);
    
  }

  return 0;
}
Step through this code


  1. Days in a month using switch()
  2. To practice using a switch()
  3. We will write a switch() statement that prints out the number of days in a month using the integer values 1-12 for the months January to December. We will do this in stages. As usual with the final mini-exercise, if you find it a little difficult finish it off in the lab class.
  4. Create a new program in the on-line compiler with a suitable title and initial comment.
  5. Declare an int variable with a suitable name to represent the month under consideration.
  6. Print a message to the screen telling the user to enter a month in the range 1 to 12, then read in the value of the month using scanf("%d", &your_variable_name).
    • Remember that the format to the scanf() should just be "%d" with no extra characters (eg not "%d\n").
  7. As a check, immediately after reading in the integer value print it out to the screen. Build & run and check the program reads in the number successfully.
  8. Now have a very small switch() statement with one case (2 for February) that prints "28" and a default that prints a helpful error message saying that the number is not a valid month. Build & run and check it works OK.
  9. Now add the cases for the other months. Check it works for a number of valid and invalid integer values. (NB: do not try entering non-integers when the code is trying to read in an integer, that's a much more advanced issue.)

Fall-through after other statements

The above example contains several adjacent cases and the "fall-through" behaviour is fairly intuitive. Much less intuitive, and much less common, is when a case has some executable statements and then falls through to the next one. A fall-through after another statement is well worth a comment as otherwise it can look like a mistake as in this somewhat contrived example:

// Demonstrate switch() consecutive labels
#include <stdio.h>

int main() {
  int num = 2;

  // Is num a prime less than 10?
  // Take special note of 2, the only even prime
  switch(num) {
  case 2: 
    printf("Even thought it is even, ");
    /* Fall through */

  case 3: case 5: case 7:
    printf("%d is a prime number less than 10\n", num);
    break;

  default:
    printf("%d is not a prime number less than 10\n", num);
    
  }

  return 0;
}
Step through this code


Summary

The text of each key point is a link to the place in the web page.

The if() statement

Relational operators

{ ... } blocks

The switch() statement

Log in
                                                                                                                                                                                                                                                                       

Validate   Link-check © Copyright & disclaimer Privacy & cookies Share
Back to top