Conditional Statements

Having read this far you will already have come across some conditional statements used in code examples. Now is the time to explore them in more detail.

The if statement

The simplest form of conditional statement is the if statement.

if(some condition) {… }

where “some condition” is usually an expression using one or more of the comparison operators. The key here is that whatever is within the brackets following the if can be evaluated as a Boolean true or false. If that expression is true then the code block immediately following will be executed. A zero value, remember, is deemed to also have the value false and (in C) any non-zero value is deemed to be true (not just 1).

int x = -37;
if (x) {
// code here will be executed as x is not zero (therefore x is true)
}
if (x < 10) {
  digitalWrite(LED_PIN, HIGH);
}

In the second example, the expression x < 10 was evaluated. If it is true then the statement between the {} curly braces is executed, otherwise not. In fact, the braces can be omitted if there is only one statement to be executed. However, I find that always using the braces aids code readability and certainly avoids the potential error of later adding a second statement expecting it to also be executed if the expression is true. A pair of braces makes explicit just what code is or is not going to be included when a condition is true.

if (x < 10) digitalWrite(LED_PIN, HIGH);
if (x < 10)
  digitalWrite(LED_PIN, HIGH);
if (x < 10) { digitalWrite(LED_PIN, HIGH); }
if (x < 10) {
  digitalWrite(LED_PIN, HIGH);
}

All the above are valid but I would strongly advise the third or fourth form shown as there is no overhead when it comes to the compiled code actually run by the Arduino board but the meaning is very clear.

One thing just on curly braces. In the first example in this section, the left brace was on the same line as the if keyword and the right brace on a line of its own following the statement executed if the condition proved true. This is commonly known as “hanging braces” and is the preferred style of JavaScript and (most) C programmers. C# programmers in contrast tend to place the left brace on a line of its own as well. Flame wars have been fought on this subject. The important thing is probably to pick a style and then be consistent. Along with judicious line indentation, braces should be seen as an aid to code readability as well as a key form of C punctuation intended to define blocks of code.

Multiple conditions can be tested within the brackets following the if keyword.

if(x < 10 || x == y) {} will return true if x is less than 10 OR x is equal to y. Brackets can be used to combine multiple tests if required. Something like:

if(x < 10 || (x==y && y > 3)) {} where the second set of conditions must both be true OR just the first.

There is a common error made where a programmer mistypes the equality conditional operator and ends up with the assignment operator.

if(x = 2) {}

The above conditional test will always evaluate to true. The variable x is assigned the value 2 and 2 is not zero so will in turn be evaluated as true. This is a common program bug. That last conditional statement should probably have been:

if(x == 2) {}

As a conditional expression is evaluated to true or false then it is perfectly valid to just use a Boolean value presented as a variable or a constant.

if(true) {} or even if( !false){} where both conditions return true (not false in the latter case). The keywords true and false are both built in Boolean constants used to aid code readability.

The if statements used so far have allowed the conditional execution of one or more program statement. If we want to execute some alternate statements if the condition is not met then the else keyword is used.

if (x < 10) {
  digitalWrite(LED_PIN, HIGH);
} else {
  digitalWrite(LED_PIN, LOW);
}

  illustrates the usage. If the variable x has a value less than 10 then the LED_PIN is set HIGH otherwise the LED_PIN is set LOW.

Conditional statements can be nested and combined.

if(analogRead(TEMP_PIN) < 500) {                   // 1
  if(analogRead(HUMID_PIN) < 250){                 // 2
    digitalWrite(LED_PIN, LOW);
  }
} else if(analogRead(HUMID_PIN) > 780) {           // 3
    digitalWrite(LED_PIN, HIGH);
} else {
  tone(BUZZ_PIN, 1000);
}

If the first condition is true then the second condition is tested. If the first condition is false then the third condition is tested. If the first and third are false then the final else{} clause executes and sounds a tone [Note, the tone() function is only available on selected Arduino models and is introduced later in this book.]

Where an if statement contains multiple conditions then they are generally evaluated left to right although operator precedence will have an influence just as in arithmetic expressions. In addition, a conditional expression may be “short-circuited” where the result can be determined before all of the elements have been evaluated. A couple of examples will probably help clarify.

if (a == b && b == c) {…} // if a is not equal to b then b==c will not be evaluated
if (a == 3 || b > 7) {…} // if a equals 3 then b > 7 will not be evaluated.

The == and > operators have a higher precedence than the && or || operators so (in the first example) the b == c expression would always be evaluated before the && operator was applied.

If you end up with a complex set of if conditions and else statements then you might consider the option of using a switch statement, coming up next.

switch / case

A switch statement also controls the flow of a program but manages the process by comparing the value of a variable to specific cases. A case can be a single value or a range of values. A simple program example will explain more.

void setup() {
    Serial.begin(115200);
    Serial.println("Type me a number 0 to 2");
}

void loop() {
    byte nChar = 0;
    if (Serial.available() > 0) {
        nChar = Serial.read();
        switch(nChar){
          case '0':
            Serial.println("Lets go higher");
            break;
          case '1':
            Serial.println("Getting started");
            break;
          case '2':
            Serial.println("Thats my number");
            break;
          default:
            Serial.println("Just 0 to 2, please");
            break;
        }
    }
}

The code looks for a character sent from the Serial Monitor. If one is received it is passed to the switch statement. The value of the character is matched to the various values specified in each of the case statements (note each case statement ends with a colon). If a match is found then all following lines of code are executed until a break statement is encountered. The break statement exits the switch conditional statement block and resumes at the next line following the closing brace. If a match is not found then the code following any default statement is executed. A default statement is optional. If there is no match found and no default statement then no code within the switch {} block is executed.

If a break statement is not encountered following a match to a value in a case statement then code execution drops through the next case statement and continues executing the code lines following until a break statement is finally encountered or the end of the switch {} code block is reached. This is one way of implementing a match to one specified value OR another specified value. As an alternative, an ellipsis … can be used to specify a range of values for a given case statement. The two approaches can be seen in the following sample code where the variable v is an integer type.

switch(v) {
    case 0:
      // code for v == 0
      break;
    case 1:
      // any special code for v == 1
    case 2:
      // code for v == 1 or v == 2
      break;
    case 3 ... 7:
      // code for v >= 3 && v <= 7
      break;
    case 8 ... 10:
      // code for v >= 8 && v <= 10
      break;
    default:
      // code where v is outside the range 0 to 10 inclusive
      break; 
}

There is no specific limit on the number of lines of code that can follow any (or all) of the case statements within the overall switch conditional statement block. Common sense should probably prevail though as long code blocks will probably be difficult to follow. Consider calling a function to deal with lengthy processing resulting from a particular case.

There is one additional rule to bear in mind. If you need to declare or define a variable in the code following a case statement then the code block needs to be written within a pair of curly braces which will define the variable scope. This small sample shows how:

switch(v) {
    case 0:
      break;
    case 2:
      {
        int x = 34;
        v = aFunction(x + v);
      }
      break;
    … etc …
}

In effect, you can assume there are imaginary curly braces defining the limits of all code lines between a case statement and a following break statement but that they need to be explicitly included if a local variable needs to be declared.

In practical terms, and even after any optimisation applied by the compiler, a switch statement is likely to be faster than an if/else statement structure. An if statement has greater flexibility when it comes to expressing the conditional component but switch/case could be an order of magnitude faster in operation. This could be important if a given conditional statement is tested frequently.

Ternary operator

In our list of operators there was mention of the ternary operator “?” and this operator is used to code conditional expressions. The format is:

(condition) ? value_if_true : value_if_false;

 where there is a condition to be evaluated to a Boolean true or false followed by the ternary operator. The operator is followed by a value to be returned by the expression if the condition is true, then a colon separator followed by the value to be returned if the condition tests false.

Used judiciously this conditional operator can enhance code readability. In addition, it is the only way I can think of to conditionally define a constant. So, two examples:

const int x = (y > 1) ? 5 : 10;

Serial.println((y == 1) ? "y equals 1" : "y not equal to 1");

In the first example, if y was greater than 1 then the const x would be set to 5 but to 10 for all other values of y.

In the second example line, if y equals 1 then the string “y equals 1” would be passed to the Serial.println() method and where Y is not equal to 1 then “y not equal to 1” would be passed to the Serial method.

After the conditional expression has been evaluated then either the expression before or after the colon is evaluated. The other element is ignored. This can be important if you were relying on both of the expressions being evaluated and indirectly carrying out some other task.

It is possible to embed ternary conditional statements within ternary conditional statements but that rather undermines the pursuit of code readability. Best avoided unless you are trying to write unreadable code. [There are obfuscation competitions for C programmers if you fancy trying your hand at this.]

Conditional statements are used to direct the flow of a program based upon specified tests on one or more value. We now need to consider how we direct a program to repeat sections of code. So we are going to explore the various forms of code loops.

Loops

Loops are a generic term for code blocks that are repeated – sometime forever and sometimes for a set number of iterations or again perhaps until a condition is met.

The for Loop

The first format for repeated code to explore is the for loop. This is normally used to execute a block of code a specified number of times. This is managed using a counter which is incremented (or sometimes decremented) each time the block of code is executed.

The general format is as follows:

for(initialise counter; test if complete; increment counter) {
// code to be repeatedly executed goes here
}

which is perhaps not very clear so, a code sample might help.

void setup() {
    Serial.begin(115200);
    for(int lp = 1; lp < 11; lp++) {
      Serial.println(lp);
    }
}

This would output the numbers 1 to 10 to the Serial Monitor. This happens because the variable lp starts with a value of 1 which is sent to the Serial Monitor. Then lp is incremented by 1 and the value tested to ensure it is less than 11. If it is, then the value is again sent to the Serial Monitor. This repeats until lp is incremented to 11 when the process exits the loop.

The loop “counter” does not have to be defined within the for statement. The following is just as valid.

void setup() {
    Serial.begin(115200);
    int lp = 1;
    for(; lp < 11; lp++) {
      Serial.println(lp);
    }
}

Note the empty loop initialisation part, marked by the semicolon.

More than one variable can be initialised by a for loop statement so we could have written

void setup() {
    Serial.begin(115200);
    for(int lp = 1, j = 11; lp < j; lp++) {
      Serial.println(lp);
    }
}

where 2 integers are initialised in the first part of the for loop control statement with the second variable being used as part of the conditional test designed to control the number of iterations performed. This approach is commonly used where the value used in the completeness test is derived from a calculation and the programmer wants to avoid the overhead of repeating that calculation for every iteration.

The code could be changed to reverse the number sequence sent to the Serial Monitor

void setup() {
    Serial.begin(115200);
    for(int lp = 10, j = 0; lp > j; lp--) {
      Serial.println(lp);
    }
}

There is no rule that the control variable should only be incremented (or decremented) by 1. There might be many occasions when another value would be more appropriate.

These examples have all used an int variable to control the loop iterations but any numeric type is valid including float. Also, the controlling variable can be adjusted using any valid expression and the conditional test can include any other variable in scope. This makes for loops in C much more powerful than those available to many other programming languages. The following example could be used to light a LED connected to an Arduino with increasing brightness and then fade it away back to off.

for (int i = 0, x = 1; i > -1; i += x){
      analogWrite(LED_PIN, i);
      if (i == 255){
        x = -1; // reverse x
      }
      delay(10);
}

[N.B. The above code will not fade the built in LED on an Arduino board as that is connected to a digital PIN which is either HIGH or LOW but would work for an LED connected to pins 9 to 11 (among others) on a Uno.]

Any or all of the component expressions within the brackets following the for keyword may be omitted. The following loop will execute forever or until a break statement is encountered.

for(; ;) {
  //do some stuff
}

The above format is a bit clumsy and most programmers would implement a continuous loop using a while statement (coming shortly). First though, we had better look at the break statement.

The break Statement

The code can make an early exit from a loop by using a break statement. The break statement (first seen in the switch/case conditional statement) breaks out of the loop and continues with code execution following the loop.

int x = 0, y = 9;
for(int lp = 10, j = 0; lp > j; lp--) {
     /* some code here
     *  that might change x or y
     */
    if(x == y) {
      break;
    }
}

The continue Statement

The continue statement short-cuts any remaining statements in a loop code block and forces the loop to iterate (that is, increment the counter, apply the conditional test and restart at the top of the loop code block, if appropriate). It is a very effective and highly readable way of conditionally avoiding a block of code without a complex if/else statement set.

void setup() {
  Serial.begin(115200);
  int x = 0, y = 9;
  for(int lp = 10, j = 0; lp > j; lp--) {
    if(lp == 5 || lp == 6) {
      continue;
    }
    Serial.println(lp);
   }
}

In the above example the continue statement would skip sending the values 5 and 6 to the Serial Monitor.

The while loop

You might want to repeat a block of code while a condition holds true. The best way to achieve that is to use the while loop. The format is:

while (condition is true) {
  // do some stuff
}

The code inside the curly braces is executed if the condition is true and the conditional test is repeated every time the program flow reaches the end of the code block at the right hand } brace.

The following function uses a while loop to help calculate the factorial of an int variable x. We have not covered functions properly yet but they are coming up shortly. For the moment just concentrate on the while loop.

unsigned long calcFactorial(int x) {
  unsigned long factorial = 1;
  while (x > 0) {
    factorial *= x;
    --x;
  }
  return factorial;
}

[N.B. The factorial of a number is the product of an integer and all of the positive integers below it. Factorial 8 (often written 8!) is 40,320. Factorials get large very quickly.]

Both break and continue statements can be used to control execution within a while loop just as in a for loop.

A common way of writing a loop that does not terminate unless a break statement is encountered is:

while(true) {
  // code statements
}

Which is perhaps how the standard loop() function required in an Arduino program would be implemented if it was not built in.

The do while Loop

The do while loop is like an upside down while loop as the conditional test used to exit the loop sits at the bottom of the code block. However, there is one additional feature in that the code block is always executed at least once – even if the conditional statement evaluates to false. The format is

do {
  // program statements here
} while (condition is true)

An example might be:

do   
{  
    y = someFunction( x );  
    x--;  
} while ( x > 0 );

which will always set the variable y to the value returned by somFunction(x) and then may continue to do so while x is greater than zero. The loop will exit when (x > 0) evaluates to false.

Both break and continue statements are valid within a do loop just as in a for or while loop.

The goto statement

The goto statement has a rich and very definitely controversial history in programming. Some of us are old enough to have written commercial programs using a language where the goto statement was the only way to control the overall flow of a program. The trouble was that programs written with that restriction often became convoluted and muddled. As soon as alternative structures became available the use of goto became virtually forbidden among users of high level programming languages. Even after many years, experienced programmers will warn those in training to never use goto – safe in the knowledge that this restriction did not apply to themselves. Used judiciously it can be useful although purists can always find a way of avoiding it.

The statement format is

goto labelName;

where labelName is a label found in the code within the current function. You can’t use goto to jump to a label embedded in code in another function.

A label can be inserted into the code as a line consisting of the label name followed by a colon (:). The label name can’t be a C reserved word, must be unique within a given function but can, strangely, be the same as a variable name – even one in scope.

The most common modern use for goto is to extricate the flow of a program from within a set of nested loops. The break statement can be used to exit from the current loop but the goto can help exit from an enclosing loop. Here is some very contrived example code to illustrate the usage:

void doSome(int x) {
  for(int i = 0; i < 10; i++){
      for(int j = 10; j > 0; j--){
          for(int k = 0; k < 6 ; k++){
              if (x < k){
                goto final;
              }
              /*  do some other stuff
               *   ...
               */
          }
      }
  }
final:
}

Generally, the use of goto is to be avoided but may have a place where it simplifies your code.

Code downloads for this chapter are available here.