Conditionals – C

Conditionals are for controlling program flow which in simple terms means making the program flexible, making decisions based on set conditions. C language supports multiple ways to make decisions for controlling program flow – if, else if, else, switch and ternary operator. It is possible to nest all of the fore mentioned language constructs – putting them inside one another, imagine those constructs being like Matryoshka dolls.

Each of the conditionals have similar structure – expression which will be evaluated as true (All values which are not 0, or “false”) or false (value of 0 or NULL) and then the following code block which will only be executed when the expression is considered to be true.

if

If statement is just a question which could be answered with true or false. Whenever the question is answered with a true value, the code block following it will be executed, otherwise it will be ignored.

Syntax:

if (expression) {
    /* Code block */
}

Representation in UML:

if

If statements:

  • have a condition statement (a “question”) which will be evaluated as true or false
    • Following code block will be executed when true
    • Following code block will be ignored when false
  • have following code block, which is surrounded by curly braces and indented one level deeper
  • can be used without else if and else statements.
  • can be nested
    • Consider using more complex expression when having too many nested ifs
    • Generally 3 nests is considered the upper limit
  • can have following else if statements
  • can have following else statement

else if

Else if statement works similarly to if, however preceding it should be an if statement. However there can be multiple else if statements. In case of multiple else if statements for one  code block to execute, the previous if and else if statements must be evaluated as false.

Syntax:

if (expression) {
    /* Code block */
} else if (expression2) {
    /* Other block*/
}

Representation in UML:

else if

Else if statements:

  • have preceding if statement
  • current else if condition will only be checked if previous if and else if conditions were false
    • if true, current code block will execute, and no following else/else if statements will execute
  • can have multiple following else if statements
    • consider using switch instead of if, else if, else when you have a lot of else if’s
  • can have following else statement
  • of a series of if, else if statements, only 1 will ever execute

else

Else statement is without expression and is always preceded by either if or else if statement. It is a good practice to have else after else if statement as a ‘last resort’ action.

Syntax:

// if, else if, else
if (expression) {
    /* Code block */
} else if (expression2) {
    /* Other block*/
} else {
    /* Last block */
}
// OR

// if, else
if (expression) {
    /* Code block */
} else {
    /* Other block */
}

Representation in UML:

if else

Else statements:

  • are preceded by if or else if statements
  • do not have condition
  • can be only one in a sequence of if, else if, else statements
  • execute as a “default action” if all other conditions are false

switch

Switch is similar to having if, multiple else if’s and else, however it will behave slightly differently – it has an expression which will be evaluated to some value and then code execution will jump to the case with constant expression which has the same value. There is one fundamental difference between switch and if, else if, else construction – by default it acts as fallthrough. This means that every case after the matching case will be executed, however this can be prevented by ending each code block with break statement. This behavior is very useful in some cases (e.g. Duff’s device ).

Syntax:

// Fallthrough
switch (expression) {
    case expression1:
        /* Code block 1 */
    case expression2:
        /* Code block 2 */
    case expression3:
        /* Code block 3 */
    default:
        /* Default code block*/
}

switch (expression) {
    case expression1:
        /* Code block 1 */
        break;
    case expression2:
        /* Code block 2 */
        break;
    case expression3:
        /* Code block 3 */
        break;
    default:
        /* Default code block*/
}

Representation in UML:

switch Fallthrough
switch with breaks

Switch statements:

  • have expression which will be evaluated
    • integer or enumerated type
  • have n number of cases with constant expression
    • same data type as the expression in the switch statement
  • by default behave as fallthrough
    • after the matching case all the following cases will be executed (until the end or break)
    • can be avoided by adding break at the end of case’s code block
  • must have at least one case statement
  • can have default case
    • will be executed if none of the cases were matched
    • good practice to have a default case
  • evaluate expression once and then jump to case with according constant value
  • can replace multiple if, else if, else statements for improved readability
  • can be nested

ternary operator

Ternary operator behaves similarly to if, else however after evaluating the whole expression, it will have value based on the expression in the first part. This operator contains 3 parts, expression, statement when expression is evaluated as true and statement when expression is evaluated as false.

Syntax:

expression ? true_statement : false_statement;

Ternary operators:

  • are usually used when assigning a value
  • are useful when writing concise code
  • should be avoided if possible as it introduces less readable code

numbers as conditions

The condition, or “question” in your if/else if statement is, fundamentally, a number.  if that number is 0, then the condition is “false”  if that number is anything else, then that condition is true.   this is important in cases where the “question” we want to ask is not as simple as comparing two numbers, for example:

  • checking to see if a file opened successfully
    • fopen() returns 0 pointer if it fails, which evaluates as “false”
  • checking to see if two strings are the same
    • strcmp() returns 0 if the two strings compared are identical, which evaluates as “false”
  • and many other cases

 

Examples and design decisions

Most of the cases with conditionals it is reasonable to think whether to nest them or not. If your nestedness exceeds 3 levels it might be a good  idea to review your program structure and think whether it is optimal to have this many nested conditional statements.

Nested ifs

Consider the two following examples, both are equal however second can be more comprehensible, especially when there is a lot of code between the nested if’s.

if (expression1) {
    if (expression2) {
        if (expression3) {
            /* Code block */
        }
    }
}

if (expression1 && expression2 && expression3) {
    /* Code block */
}

Switch vs if, else if, else

Consider the two options when control flow depends only on one variable, the control flow could be easily described with switch statement. This would decrease the number of comparisons in the worst case and would improve readability.

if (value == 0) {
    /* Code block 0 */
} else if (value == 1) {
    /* Code block 1 */
} else if (value == 2) {
    /* Code block 2 */
} else if (value == 3) {
    /* Code block 3 */
} else if (value == 4) {
    /* Code block 4 */
} else if (value == 5) {
    /* Code block 5 */
} else {
    /* Default code block */
}

switch (value) {
    case 0:
        /* Code block 0 */
        break;
    case 1:
        /* Code block 1 */
        break;
    case 2:
        /* Code block 2 */
        break;
    case 3:
        /* Code block 3 */
        break;
    case 4:
        /* Code block 4 */
        break;
    case 5:
        /* Code block 5 */
        break;
    default:
        /* Default code block*/
}

Resources

  1. Decision making [ Link ]
  2. Coding Style [ Link ]