C Programming Basics:
Part 4 – Control Flow

Welcome to the fourth installment of our C programming tutorial series! So far, we’ve covered the fundamentals of C, variables and data types, and operators and expressions. Now, we’re ready to explore control flow – the mechanisms that allow your programs to make decisions, execute code conditionally, and repeat operations.

Control flow is what gives your programs their intelligence and efficiency. Without it, programs would simply execute instructions in order from top to bottom, without any ability to adapt to different situations or inputs.

Decision Making: Conditional Statements

Conditional statements allow your program to execute different code blocks based on whether a given condition is true or false.

The if Statement

The simplest form of conditional execution is the if statement:

C
if (condition) {
    // Code to execute if condition is true
}

The condition is any expression that evaluates to a value. In C, any non-zero value is considered true, while zero is considered false.

Example:

C
#include <stdio.h>

int main() {
    int age;
    
    printf("Enter your age: ");
    scanf("%d", &age);
    
    if (age >= 18) {
        printf("You are an adult.\n");
    }
    
    return 0;
}

The if-else Statement

To specify an alternative action when the condition is false, use the if-else statement:

C
if (condition) {
    // Code to execute if condition is true
} else {
    // Code to execute if condition is false
}

Example:

C
#include <stdio.h>

int main() {
    int number;
    
    printf("Enter a number: ");
    scanf("%d", &number);
    
    if (number % 2 == 0) {
        printf("%d is even.\n", number);
    } else {
        printf("%d is odd.\n", number);
    }
    
    return 0;
}

The if-else if-else Ladder

For multiple conditions, you can use the if-else if-else ladder:

C
if (condition1) {
    // Code to execute if condition1 is true
} else if (condition2) {
    // Code to execute if condition1 is false but condition2 is true
} else if (condition3) {
    // Code to execute if condition1 and condition2 are false but condition3 is true
} else {
    // Code to execute if all conditions are false
}

Example:

C
#include <stdio.h>

int main() {
    int score;
    
    printf("Enter your score (0-100): ");
    scanf("%d", &score);
    
    if (score >= 90) {
        printf("Grade: A\n");
    } else if (score >= 80) {
        printf("Grade: B\n");
    } else if (score >= 70) {
        printf("Grade: C\n");
    } else if (score >= 60) {
        printf("Grade: D\n");
    } else {
        printf("Grade: F\n");
    }
    
    return 0;
}

Let’s visualize this decision-making process:

flowchart TD
    A[Enter score] --> B{score >= 90?}
    B -- Yes --> C[Grade: A]
    B -- No --> D{score >= 80?}
    D -- Yes --> E[Grade: B]
    D -- No --> F{score >= 70?}
    F -- Yes --> G[Grade: C]
    F -- No --> H{score >= 60?}
    H -- Yes --> I[Grade: D]
    H -- No --> J[Grade: F]

Nested if Statements

graph TD;
    A[Start] --> B{Condition 1}
    B -- True --> C{Condition 2}
    B -- False --> E[Else Block]
    C -- True --> D[Execute Inner Block]
    C -- False --> F[Else of Condition 2]
    D --> G[End]
    F --> G
    E --> G

You can also place if statements inside other if or else blocks:

C
if (condition1) {
    if (condition2) {
        // Code to execute if both condition1 and condition2 are true
    } else {
        // Code to execute if condition1 is true but condition2 is false
    }
} else {
    // Code to execute if condition1 is false
}

Example:

C
#include <stdio.h>

int main() {
    int age;
    char has_license;
    
    printf("Enter your age: ");
    scanf("%d", &age);
    
    printf("Do you have a driver's license? (Y/N): ");
    scanf(" %c", &has_license);  // Note the space before %c to consume any whitespace
    
    if (age >= 18) {
        if (has_license == 'Y' || has_license == 'y') {
            printf("You can drive.\n");
        } else {
            printf("You are old enough, but you need a license to drive.\n");
        }
    } else {
        printf("You are too young to drive.\n");
    }
    
    return 0;
}

The switch Statement

graph TD;
    A[Start] --> B{Switch on expression}
    B -->|Case 1| C[Execute Case 1]
    B -->|Case 2| D[Execute Case 2]
    B -->|Case N| E[Execute Case N]
    B -->|Default| F[Execute Default Case]
    C --> G[Break]
    D --> G
    E --> G
    F --> G
    G --> H[End]

When you have multiple conditions based on the value of a single variable, the switch statement provides a cleaner alternative to the if-else if-else ladder:

C
switch (expression) {
    case constant1:
        // Code to execute if expression equals constant1
        break;
    case constant2:
        // Code to execute if expression equals constant2
        break;
    // More cases...
    default:
        // Code to execute if expression doesn't match any case
}

The break statement is crucial – it causes execution to jump out of the switch block. Without it, execution would “fall through” to the next case.

Example:

C
#include <stdio.h>

int main() {
    char grade;
    
    printf("Enter your grade (A, B, C, D, or F): ");
    scanf(" %c", &grade);
    
    switch (grade) {
        case 'A':
        case 'a':
            printf("Excellent!\n");
            break;
        case 'B':
        case 'b':
            printf("Good job!\n");
            break;
        case 'C':
        case 'c':
            printf("Satisfactory.\n");
            break;
        case 'D':
        case 'd':
            printf("Needs improvement.\n");
            break;
        case 'F':
        case 'f':
            printf("Failed.\n");
            break;
        default:
            printf("Invalid grade.\n");
    }
    
    return 0;
}

The switch statement in C has some limitations:

  • The expression must evaluate to an integer type (char, int, etc.)
  • Case labels must be constants or constant expressions, not variables
  • You can only test for equality, not ranges or other conditions

Conditional Operator (Ternary Operator)

graph TD;
    A[Start] --> B{Condition ?}
    B -- True --> C[Execute Expression1]
    B -- False --> D[Execute Expression2]
    C --> E[End]
    D --> E

As we saw in the previous article, the conditional operator (? :) provides a compact way to express simple if-else statements:

C
condition ? expression_if_true : expression_if_false

Example:

C
#include <stdio.h>

int main() {
    int age;
    
    printf("Enter your age: ");
    scanf("%d", &age);
    
    printf("You are %s.\n", age >= 18 ? "an adult" : "a minor");
    
    return 0;
}

Loops: Repetitive Execution

Loops allow you to execute a block of code repeatedly, which is essential for tasks like processing large amounts of data, implementing algorithms, or performing repetitive calculations.

C provides three primary loop constructs:

The while Loop

The while loop continues executing a block of code as long as the condition remains true:

C
while (condition) {
    // Code to execute while condition is true
}

The condition is evaluated before each iteration. If it’s initially false, the loop body won’t execute at all.

Example:

C
#include <stdio.h>

int main() {
    int count = 1;
    
    while (count <= 5) {
        printf("Count: %d\n", count);
        count++;
    }
    
    return 0;
}

This program outputs:

C
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5

The do-while Loop

The do-while loop is similar to the while loop, but it evaluates the condition after executing the loop body:

C
do {
    // Code to execute while condition is true</em>
} while (condition);

This ensures that the loop body executes at least once, even if the condition is initially false.

Example:

C
#include <stdio.h>

int main() {
    int number;
    
    do {
        printf("Enter a positive number: ");
        scanf("%d", &number);
    } while (number <= 0);
    
    printf("You entered: %d\n", number);
    
    return 0;
}

This program keeps asking for input until the user enters a positive number.

The for Loop

The for loop provides a compact way to write loops with initialization, condition, and increment/decrement all in one line:

C
for (initialization; condition; update) {
    // Code to execute while condition is true
}

The execution flow is:

  1. Execute the initialization (once)
  2. Check if the condition is true
  3. If true, execute the loop body
  4. Execute the update expression
  5. Go back to step 2

Example:

C
#include <stdio.h>

int main() {
    for (int i = 1; i <= 5; i++) {
        printf("Iteration %d\n", i);
    }
    
    return 0;
}

This program outputs:

C
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 5

Comparing Loop Types

Here’s a visual comparison of the three loop types:

flowchart TD
    subgraph "while loop"
        A1[Start] --> B1{Condition?}
        B1 -- true --> C1[Loop Body]
        C1 --> B1
        B1 -- false --> D1[Exit]
    end
    
    subgraph "do-while loop"
        A2[Start] --> C2[Loop Body]
        C2 --> B2{Condition?}
        B2 -- true --> C2
        B2 -- false --> D2[Exit]
    end
    
    subgraph "for loop"
        A3[Start] --> E3[Initialization]
        E3 --> B3{Condition?}
        B3 -- true --> C3[Loop Body]
        C3 --> F3[Update]
        F3 --> B3
        B3 -- false --> D3[Exit]
    end

Infinite Loops

An infinite loop occurs when the loop condition always evaluates to true, causing the loop to run indefinitely:

C
// Infinite while loop
while (1) {
    // This will run forever unless broken
}

// Infinite for loop
for (;;) {
   // This will run forever unless broken
}

Infinite loops aren’t always mistakes. They’re sometimes used intentionally in programs that need to run continuously, like operating systems or embedded systems. However, you need a way to break out of them when necessary.

Loop Control Statements

C provides statements to alter the normal flow of loops:

The break Statement

graph TD
    A[Start Loop] --> B{Condition Check};
    B -- True --> C[Execute Code Block];
    C --> D{Encounter 'break'};
    D --> |NO|C
    D --> |YES| E[Exit Loop];
    B -- False --> E;

The break statement immediately exits the innermost loop or switch statement:

C
for (int i = 1; i <= 10; i++) {
    if (i == 5) {
        break;  // Exit the loop when i reaches 5
    }
    printf("%d ", i);
}
C
This outputs: 1 2 3 4

The continue Statement

The continue statement skips the rest of the current iteration and moves to the next iteration:

C
for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) {
        continue;  // Skip even numbers
    }
    printf("%d ", i);
}
C
This outputs: 1 3 5 7 9

The goto Statement

The goto statement allows you to jump to a labeled statement within the same function:

C
#include <stdio.h>

int main() {
    int i = 0;
    
start:
    if (i < 5) {
        printf("%d ", i);
        i++;
        goto start;
    }
    
    return 0;
}
C
This outputs: 0 1 2 3 4

While goto exists in C, it’s generally discouraged as it can make code hard to follow and maintain. Modern programming practices favor structured control flow using loops and conditionals.

graph TD
    A[Start of Code] --> B{Condition};
    B -- True --> C[Some Code];
    C --> D{Encounter 'goto label'};
    D --> E[label: Another Part of Code];
    E --> F[Continue Execution];
    B -- False --> G[End of Code];

Nested Loops

You can place loops inside other loops, creating nested loops. This is useful for working with multi-dimensional data structures or generating complex patterns:

C
#include <stdio.h>

int main() {
    // Print a simple multiplication table
    for (int i = 1; i <= 5; i++) {
        for (int j = 1; j <= 5; j++) {
            printf("%d\t", i * j);
        }
        printf("\n"); // New line after each row
    }
    
    return 0;
}

This produces:

C
1	  2	  3	  4	  5	
2	  4	  6	  8	  10	
3	  6	  9	  12	15	
4	  8	  12	16	20	
5	  10	15	20	25	

Each iteration of the outer loop (i) triggers a complete execution of the inner loop (j).

Practical Examples

Let’s look at some practical examples that demonstrate control flow in C:

Example 1: Calculating Factorial

C
#include <stdio.h>

int main() {
    int n, factorial = 1;
    
    printf("Enter a positive integer: ");
    scanf("%d", &n);
    
    if (n < 0) {
        printf("Error: Factorial is not defined for negative numbers.\n");
    } else {
        for (int i = 1; i <= n; i++) {
            factorial *= i;
        }
        printf("%d! = %d\n", n, factorial);
    }
    
    return 0;
}

Example 2: Finding Prime Numbers

C
#include <stdio.h>
#include <stdbool.h>

int main() {
    int n;
    
    printf("Enter a positive integer: ");
    scanf("%d", &n);
    
    if (n <= 1) {
        printf("%d is not a prime number.\n", n);
        return 0;
    }
    
    bool is_prime = true;
    
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            is_prime = false;
            break;
        }
    }
    
    if (is_prime) {
        printf("%d is a prime number.\n", n);
    } else {
        printf("%d is not a prime number.\n", n);
    }
    
    return 0;
}

Example 3: Simple Menu System

C
#include <stdio.h>

int main() {
    int choice;
    bool running = true;
    
    while (running) {
        printf("\nMenu:\n");
        printf("1. Say hello\n");
        printf("2. Calculate square\n");
        printf("3. Check even/odd\n");
        printf("4. Exit\n");
        printf("Enter your choice (1-4): ");
        scanf("%d", &choice);
        
        switch (choice) {
            case 1:
                printf("Hello, world!\n");
                break;
            case 2: {
                int num;
                printf("Enter a number: ");
                scanf("%d", &num);
                printf("Square of %d is %d\n", num, num * num);
                break;
            }
            case 3: {
                int num;
                printf("Enter a number: ");
                scanf("%d", &num);
                if (num % 2 == 0) {
                    printf("%d is even.\n", num);
                } else {
                    printf("%d is odd.\n", num);
                }
                break;
            }
            case 4:
                printf("Goodbye!\n");
                running = false;
                break;
            default:
                printf("Invalid choice. Please enter a number between 1 and 4.\n");
        }
    }
    
    return 0;
}

Common Pitfalls and Best Practices

Off-by-One Errors

One of the most common errors in loops is the “off-by-one” error, where a loop iterates one too many or one too few times:

C
// Correct: Prints numbers 1 to 10
for (int i = 1; i <= 10; i++) {
    printf("%d ", i);
}

/ Incorrect: Prints numbers 0 to 9
for (int i = 0; i < 10; i++) {
    printf("%d ", i + 1); // Adding 1 to fix the output, but the logic is still confusing
}

Infinite Loops

Always ensure your loops have a valid exit condition:

C
// Potential infinite loop if user never enters a valid value
while (true) {
    printf("Enter a positive number: ");
    scanf("%d", &num);
    
    if (num > 0) {
        break;
    }
    
    printf("Invalid input. Try again.\n");
}

Loop Variable Scope

In C99 and later, you can declare variables in the initialization part of a for loop:

C
for (int i = 0; i < 10; i++) {
    // i is only visible inside the loop
}
// i is not accessible here

In earlier C standards, you needed to declare the variable before the loop:

C
int i;
for (i = 0; i < 10; i++) {
    // Loop body
}
// i is still accessible here

Choosing the Right Loop

  • Use for when you know the number of iterations in advance
  • Use while when you don’t know how many iterations you need
  • Use do-while when the loop body must execute at least once

Conclusion

Control flow is what makes C programs dynamic and responsive. With conditional statements, you can make your programs adapt to different situations. With loops, you can efficiently process data and perform repetitive tasks. Mastering these concepts is essential for writing effective C programs.

In the next part of our series, we’ll explore functions in C, which allow you to organize your code into reusable, modular units.

Practice Exercises

  1. Write a program that prints the first n terms of the Fibonacci sequence.
  2. Create a program that checks if a number is a palindrome (reads the same backward as forward).
  3. Write a program that determines whether a year entered by the user is a leap year.
  4. Create a simple number guessing game where the program generates a random number between 1 and 100, and the user tries to guess it with feedback after each guess.
  5. Write a program that prints a pattern like this for a given number of rows:
C
*
**
***
****
*****

Happy coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top