Chapter 03

Control Flow

Directing program execution with conditions, loops, and branching statements

What You Will Learn

  • How to make decisions with if-else statements
  • Nested and chained conditional statements
  • The ternary conditional operator
  • Multi-way branching with switch
  • All types of loops: while, do-while, for, and range-based for
  • Nested loop patterns
  • Controlling loops with break and continue
  • Best practices for clean control flow

Prerequisites

  • Completed Chapters 01 and 02
  • Understanding of variables, data types, and operators

If-Else Statements

The if statement executes code only when a condition is true.

Basic If Statement

int age = 18;

if (age >= 18) {
    std::cout << "You are an adult.\n";
}

If-Else

int score = 75;

if (score >= 60) {
    std::cout << "You passed!\n";
} else {
    std::cout << "You failed.\n";
}

If-Else-If Chain

int score = 85;

if (score >= 90) {
    std::cout << "Grade: A\n";
} else if (score >= 80) {
    std::cout << "Grade: B\n";
} else if (score >= 70) {
    std::cout << "Grade: C\n";
} else if (score >= 60) {
    std::cout << "Grade: D\n";
} else {
    std::cout << "Grade: F\n";
}

If with Initializer (C++17)

You can declare and initialize a variable within the if statement:

// The variable is scoped to the if-else block
if (int result = calculate(); result > 0) {
    std::cout << "Positive: " << result << "\n";
} else {
    std::cout << "Non-positive: " << result << "\n";
}
// 'result' is not accessible here

// Useful for checking map lookups
std::map<std::string, int> ages = {{"Alice", 30}};
if (auto it = ages.find("Alice"); it != ages.end()) {
    std::cout << "Found: " << it->second << "\n";
}

Single-Statement If (No Braces)

// Legal but not recommended
if (x > 0)
    std::cout << "Positive\n";

// This can lead to bugs:
if (x > 0)
    std::cout << "Positive\n";
    std::cout << "This always prints!\n";  // NOT part of the if!

// Always use braces for clarity:
if (x > 0) {
    std::cout << "Positive\n";
}

Tip

Always use braces { } even for single statements. It prevents bugs and makes the code clearer.

Nested Conditions

If statements can be nested inside each other for complex logic.

int age = 25;
bool hasLicense = true;

if (age >= 18) {
    if (hasLicense) {
        std::cout << "You can drive.\n";
    } else {
        std::cout << "You need a license.\n";
    }
} else {
    std::cout << "You are too young to drive.\n";
}

Combining Conditions

Often, nested conditions can be combined using logical operators:

// Instead of nested if:
if (age >= 18 && hasLicense) {
    std::cout << "You can drive.\n";
}

// Multiple conditions
if (age >= 18 && age <= 65 && hasLicense) {
    std::cout << "Standard driver\n";
}

// Using || (OR)
if (isAdmin || isModerator) {
    std::cout << "Access granted\n";
}

// Complex conditions (use parentheses for clarity)
if ((role == "admin" || role == "mod") && isActive) {
    grantAccess();
}

Short-Circuit Evaluation

Logical operators evaluate left-to-right and stop early if possible:

// Safe null pointer check
if (ptr != nullptr && ptr->isValid()) {
    // ptr->isValid() is only called if ptr != nullptr
}

// If first condition is false, second isn't evaluated
if (false && expensiveFunction()) {
    // expensiveFunction() never runs
}

// If first condition is true, second isn't evaluated
if (true || expensiveFunction()) {
    // This block runs, expensiveFunction() never runs
}

Ternary Conditional Operator

The ternary operator ?: is a compact way to write simple if-else expressions.

Basic Syntax

// condition ? value_if_true : value_if_false

int a = 10, b = 20;
int max = (a > b) ? a : b;  // max = 20

std::string status = (score >= 60) ? "Pass" : "Fail";

Ternary in Output

int age = 17;
std::cout << "You are " << (age >= 18 ? "an adult" : "a minor") << "\n";

Nested Ternary (Use Sparingly)

int score = 85;
std::string grade = (score >= 90) ? "A" :
                    (score >= 80) ? "B" :
                    (score >= 70) ? "C" :
                    (score >= 60) ? "D" : "F";

// Prefer if-else for complex logic - nested ternary is hard to read

Ternary as Lvalue

int a = 5, b = 10;
(a > b ? a : b) = 100;  // Assigns to b since b is larger
// Now b = 100

Type Requirements

// Both branches must have compatible types
auto result = condition ? 1 : 2.5;      // OK: int promoted to double
auto result = condition ? "yes" : "no"; // OK: both const char*

// This doesn't work:
// auto result = condition ? 1 : "hello";  // Error: incompatible types

Switch Statement

Switch provides multi-way branching based on a single value. It's often clearer than long if-else-if chains.

Basic Switch

int day = 3;

switch (day) {
    case 1:
        std::cout << "Monday\n";
        break;
    case 2:
        std::cout << "Tuesday\n";
        break;
    case 3:
        std::cout << "Wednesday\n";
        break;
    case 4:
        std::cout << "Thursday\n";
        break;
    case 5:
        std::cout << "Friday\n";
        break;
    case 6:
    case 7:
        std::cout << "Weekend\n";
        break;
    default:
        std::cout << "Invalid day\n";
        break;
}

Fallthrough

Without break, execution continues to the next case (fallthrough):

int month = 2;
int days;

switch (month) {
    case 1: case 3: case 5: case 7: case 8: case 10: case 12:
        days = 31;
        break;
    case 4: case 6: case 9: case 11:
        days = 30;
        break;
    case 2:
        days = 28;  // Ignoring leap years
        break;
    default:
        days = 0;
}

[[fallthrough]] Attribute (C++17)

switch (value) {
    case 1:
        doSomething();
        [[fallthrough]];  // Intentional fallthrough, suppresses warning
    case 2:
        doSomethingElse();
        break;
}

Switch with Initializer (C++17)

switch (int value = getValue(); value) {
    case 0:
        std::cout << "Zero\n";
        break;
    case 1:
        std::cout << "One\n";
        break;
    default:
        std::cout << "Other: " << value << "\n";
}

Switch with Enum

enum class Color { RED, GREEN, BLUE };
Color c = Color::GREEN;

switch (c) {
    case Color::RED:
        std::cout << "Red\n";
        break;
    case Color::GREEN:
        std::cout << "Green\n";
        break;
    case Color::BLUE:
        std::cout << "Blue\n";
        break;
    // No default needed if all cases covered
}

Switch Limitations

  • Can only switch on integral types (int, char, enum), not strings or floats
  • Cases must be constant expressions
  • Cannot declare variables directly in cases without braces
// Variables in cases need their own scope
switch (x) {
    case 1: {
        int temp = 10;  // OK: in its own block
        std::cout << temp;
        break;
    }
    case 2:
        // int temp = 20;  // Error without braces!
        break;
}

While Loop

The while loop executes as long as a condition is true. The condition is checked before each iteration.

Basic While Loop

int count = 0;

while (count < 5) {
    std::cout << count << " ";
    count++;
}
// Output: 0 1 2 3 4

While with User Input

std::string input;

std::cout << "Enter 'quit' to exit:\n";
while (input != "quit") {
    std::cout << "> ";
    std::cin >> input;
    std::cout << "You entered: " << input << "\n";
}

Infinite Loop

// Runs forever (or until break/return)
while (true) {
    // Process events
    if (shouldExit()) {
        break;
    }
}

While with Stream Input

int number;
int sum = 0;

std::cout << "Enter numbers (non-number to stop):\n";
while (std::cin >> number) {
    sum += number;
}
std::cout << "Sum: " << sum << "\n";

Do-While Loop

The do-while loop executes at least once, then continues while the condition is true. The condition is checked after each iteration.

Basic Do-While

int count = 0;

do {
    std::cout << count << " ";
    count++;
} while (count < 5);
// Output: 0 1 2 3 4

Menu Example

int choice;

do {
    std::cout << "\n=== Menu ===\n";
    std::cout << "1. Option A\n";
    std::cout << "2. Option B\n";
    std::cout << "3. Exit\n";
    std::cout << "Choose: ";
    std::cin >> choice;

    switch (choice) {
        case 1:
            std::cout << "You chose A\n";
            break;
        case 2:
            std::cout << "You chose B\n";
            break;
        case 3:
            std::cout << "Goodbye!\n";
            break;
        default:
            std::cout << "Invalid choice\n";
    }
} while (choice != 3);

Input Validation

int age;

do {
    std::cout << "Enter your age (0-120): ";
    std::cin >> age;
} while (age < 0 || age > 120);

std::cout << "Valid age: " << age << "\n";

For Loop

The for loop is ideal when you know the number of iterations in advance.

Basic For Loop

// for (initialization; condition; update)
for (int i = 0; i < 5; i++) {
    std::cout << i << " ";
}
// Output: 0 1 2 3 4

Loop Components

// Any part can be omitted (but keep semicolons)
int i = 0;
for (; i < 5; ) {
    std::cout << i << " ";
    i++;
}

// Infinite loop
for (;;) {
    // Runs forever
    break;  // Need a break to exit
}

Multiple Variables

for (int i = 0, j = 10; i < j; i++, j--) {
    std::cout << "i=" << i << ", j=" << j << "\n";
}
// i=0, j=10
// i=1, j=9
// i=2, j=8
// ... until i >= j

Counting Backwards

for (int i = 10; i >= 0; i--) {
    std::cout << i << " ";
}
// Output: 10 9 8 7 6 5 4 3 2 1 0

Step Size

// Count by 2s
for (int i = 0; i <= 10; i += 2) {
    std::cout << i << " ";
}
// Output: 0 2 4 6 8 10

Iterating Over Arrays

int arr[] = {10, 20, 30, 40, 50};
int size = sizeof(arr) / sizeof(arr[0]);

for (int i = 0; i < size; i++) {
    std::cout << arr[i] << " ";
}

Range-Based For Loop (C++11)

The range-based for loop simplifies iteration over containers and arrays.

Basic Range-Based For

int arr[] = {1, 2, 3, 4, 5};

for (int x : arr) {
    std::cout << x << " ";
}
// Output: 1 2 3 4 5

With Vectors

#include <vector>

std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

for (const std::string& name : names) {
    std::cout << name << "\n";
}

By Value, Reference, and Const Reference

std::vector<int> nums = {1, 2, 3, 4, 5};

// By value (copy) - doesn't modify original
for (int x : nums) {
    x *= 2;  // Only modifies the copy
}

// By reference - modifies original
for (int& x : nums) {
    x *= 2;  // Doubles each element
}

// By const reference - read-only, no copy
for (const int& x : nums) {
    std::cout << x << " ";
}

With auto

std::map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}};

for (const auto& pair : ages) {
    std::cout << pair.first << ": " << pair.second << "\n";
}

// With structured bindings (C++17)
for (const auto& [name, age] : ages) {
    std::cout << name << ": " << age << "\n";
}

Initializer List

for (int x : {1, 2, 3, 4, 5}) {
    std::cout << x << " ";
}

Best Practice

Use const auto& for reading elements to avoid copies. Use auto& when you need to modify elements.

Nested Loops

Loops can be nested inside each other for multi-dimensional operations.

Multiplication Table

for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= 5; j++) {
        std::cout << std::setw(4) << (i * j);
    }
    std::cout << "\n";
}
// Output:
//    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

2D Array Traversal

int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        std::cout << matrix[i][j] << " ";
    }
    std::cout << "\n";
}

Pattern Printing

// Right triangle
int n = 5;
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= i; j++) {
        std::cout << "* ";
    }
    std::cout << "\n";
}
// Output:
// * 
// * * 
// * * * 
// * * * * 
// * * * * * 

// Pyramid
for (int i = 1; i <= n; i++) {
    for (int j = n; j > i; j--) {
        std::cout << " ";
    }
    for (int k = 1; k <= (2 * i - 1); k++) {
        std::cout << "*";
    }
    std::cout << "\n";
}

Finding Pairs

std::vector<int> nums = {1, 2, 3, 4, 5};
int target = 6;

for (size_t i = 0; i < nums.size(); i++) {
    for (size_t j = i + 1; j < nums.size(); j++) {
        if (nums[i] + nums[j] == target) {
            std::cout << nums[i] << " + " << nums[j] << " = " << target << "\n";
        }
    }
}
// Output:
// 1 + 5 = 6
// 2 + 4 = 6

Break and Continue

These statements alter the normal flow of loops.

break

break immediately exits the innermost loop or switch.

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break;  // Exit loop when i is 5
    }
    std::cout << i << " ";
}
// Output: 0 1 2 3 4

continue

continue skips to the next iteration of the loop.

for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue;  // Skip even numbers
    }
    std::cout << i << " ";
}
// Output: 1 3 5 7 9

Break in Nested Loops

break only exits the innermost loop:

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 1) {
            break;  // Only exits inner loop
        }
        std::cout << "(" << i << "," << j << ") ";
    }
}
// Output: (0,0) (1,0) (2,0)

Breaking Out of Nested Loops

To break out of multiple loops, use a flag or put the loops in a function:

// Method 1: Flag variable
bool found = false;
for (int i = 0; i < 10 && !found; i++) {
    for (int j = 0; j < 10 && !found; j++) {
        if (matrix[i][j] == target) {
            std::cout << "Found at (" << i << "," << j << ")\n";
            found = true;
        }
    }
}

// Method 2: Function with return
void findTarget(int matrix[][10], int target) {
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            if (matrix[i][j] == target) {
                std::cout << "Found!\n";
                return;  // Exits function, thus both loops
            }
        }
    }
}

Labeled Statements (Avoid)

C++ doesn't have labeled break like Java. Some use goto as an alternative (generally discouraged):

for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if (condition) {
            goto done;  // Jump to label
        }
    }
}
done:
// Continue here after breaking out

Goto Statement

The goto statement jumps to a labeled statement. It's generally discouraged because it makes code harder to follow.

// Legal but not recommended
int i = 0;
loop:
    std::cout << i << " ";
    i++;
    if (i < 5) {
        goto loop;
    }
// Output: 0 1 2 3 4

Warning

Avoid goto in almost all cases. It creates "spaghetti code" that's hard to read and maintain. Use loops, functions, and structured control flow instead.

Rare Legitimate Uses

Some argue goto is acceptable for:

  • Breaking out of deeply nested loops
  • Cleanup code in C-style error handling
  • State machines (though switch is usually better)

Common Mistakes

Assignment Instead of Comparison

int x = 5;
if (x = 10) {  // Wrong! This assigns 10 to x, always true
    // ...
}
// Correct: if (x == 10)

Off-By-One Errors

int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i <= 5; i++) {  // Wrong! Should be i < 5
    std::cout << arr[i];  // arr[5] is out of bounds
}

Infinite Loops

int i = 0;
while (i < 10) {
    std::cout << i;
    // Forgot to increment i!
}

Missing break in switch

switch (x) {
    case 1:
        doA();
        // Missing break! Falls through to case 2
    case 2:
        doB();
        break;
}

Dangling Else

// Which if does the else belong to?
if (a)
    if (b)
        doSomething();
else  // Belongs to inner if, not outer!
    doSomethingElse();

// Use braces to be clear:
if (a) {
    if (b) {
        doSomething();
    }
} else {
    doSomethingElse();
}

Modifying Loop Variable in Range-For

std::vector<int> nums = {1, 2, 3};
for (int n : nums) {
    n *= 2;  // Only modifies copy, not original!
}
// nums is still {1, 2, 3}

// Use reference to modify:
for (int& n : nums) {
    n *= 2;  // Now modifies original
}

Practice Questions

  1. Write a program that checks if a number is positive, negative, or zero.
  2. Create a grade calculator that converts a numeric score to a letter grade.
  3. Write a program that prints all even numbers from 1 to 100.
  4. Create a simple calculator using switch (add, subtract, multiply, divide).
  5. Print a multiplication table (1-10) using nested loops.
  6. Write a program to find the factorial of a number using a loop.
  7. Create a number guessing game with hints ("higher" or "lower").
  8. Print the Fibonacci sequence up to n terms.
  9. Write a program that reverses the digits of an integer.
  10. Create a pattern printing program (pyramid, diamond, etc.).
  11. Write a program to check if a number is prime.
  12. Use C++17 if-with-initializer to check map lookups.