03 · Control Flow¶
Control flow statements decide which code runs, and how many times.
if / else if / else¶
#include <iostream>
int main() {
int score = 72;
if (score >= 90) {
std::cout << "Grade: A" << std::endl;
} else if (score >= 80) {
std::cout << "Grade: B" << std::endl;
} else if (score >= 70) {
std::cout << "Grade: C" << std::endl;
} else {
std::cout << "Grade: F" << std::endl;
}
// Grade: C
}
Conditions are evaluated top to bottom; the first branch whose condition is
true runs, and the rest are skipped.
switch¶
int day = 3;
switch (day) {
case 1:
std::cout << "Monday" << std::endl;
break;
case 2:
std::cout << "Tuesday" << std::endl;
break;
case 3:
std::cout << "Wednesday" << std::endl;
break;
default:
std::cout << "Some other day" << std::endl;
break;
}
// Wednesday
break stops the switch from "falling through" into the next case. Omitting
it is legal C++ and occasionally used intentionally (to group cases), but
forgetting it by accident is a classic bug — -Wall (via -Wimplicit-fallthrough
in newer compilers) will flag suspicious cases.
while loops¶
int count = 0;
while (count < 5) {
std::cout << count << " ";
count++;
}
std::cout << std::endl;
// 0 1 2 3 4
while checks the condition before each iteration, so the body might run
zero times.
do-while loops¶
int attempts = 0;
do {
std::cout << "Attempt " << attempts << std::endl;
attempts++;
} while (attempts < 3);
// Attempt 0
// Attempt 1
// Attempt 2
do-while checks the condition after the body, so it always runs at least
once — useful for things like "prompt the user, then re-prompt while input is
invalid."
for loops¶
The three parts — initialization, condition, update — run once, then check, then repeat: init → check → body → update → check → body → ... until the condition is false.
Range-based for loops¶
#include <vector>
std::vector<int> numbers = {10, 20, 30, 40};
for (int n : numbers) {
std::cout << n << " ";
}
std::cout << std::endl;
// 10 20 30 40
for (const auto& n : numbers) { // avoids copying each element
std::cout << n * 2 << " ";
}
std::cout << std::endl;
// 20 40 60 80
Range-based for (introduced in C++11) iterates directly over a container's
elements without manual indexing. std::vector is covered in depth in
Module 5; const auto& avoids copying each
element, which matters for larger types like std::string.
break and continue¶
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // exit the loop entirely
}
std::cout << i << " ";
}
std::cout << std::endl;
// 0 1 2 3 4
for (int i = 0; i < 10; i++) {
if (i % 2 != 0) {
continue; // skip the rest of this iteration
}
std::cout << i << " ";
}
std::cout << std::endl;
// 0 2 4 6 8
How It Actually Works¶
At the machine level there's no such thing as if, for, or while —
every control-flow statement the compiler sees becomes comparison
instructions plus conditional jumps. if (x > 0) { a(); } else { b(); }
lowers to roughly: compare x to 0, jump to the else block's address if
the comparison is false, otherwise fall through to a()'s code, then an
unconditional jump past the else block. The CPU has no concept of nested
braces — it only has a linear instruction stream and jumps that hop around
in it.
A for (int i = 0; i < n; i++) loop desugars into the same
initialize-check-jump-increment pattern as a while loop with a jump back to
the top; the compiler treats them as the same construct. Modern compilers
also try loop unrolling — duplicating the loop body several times to cut
down on the number of conditional jumps executed, since a mispredicted
branch (the CPU guessed wrong about whether a jump would be taken) stalls
the pipeline for several cycles. This is why tight, predictable loops (like
counting up to a fixed bound) are cheap, while loops with unpredictable
branching inside them run measurably slower than the instruction count alone
would suggest — the CPU's branch predictor cache learns patterns, and code
that behaves consistently gets consistently correct guesses.
switch on an integral type compiles differently again: with enough
contiguous cases, the compiler can emit a jump table — an array of
addresses indexed directly by the switch value, letting it jump straight to
the matching case in one lookup instead of a chain of comparisons.
🔀 See this in another language¶
Exercise¶
Write a program that loops from 1 to 50 and, for each number: prints
"FizzBuzz" if it's divisible by both 3 and 5, "Fizz" if only by 3, "Buzz" if
only by 5, and the number itself otherwise. Then write a separate loop using
std::vector<int> that sums only the even numbers in the vector {3, 8, 12,
7, 19, 24} and prints the total.