Control Statements¶
By default, a program runs one statement after another from top to bottom. Control statements let you change that flow: take one path or another based on a condition, repeat a block of code, or stop a loop early.
Three families:
| Family | Examples | Purpose |
|---|---|---|
| Conditional | if, else, else if, switch |
Choose between paths |
| Loop | while, do-while, for, range-based for |
Repeat code |
| Jump | break, continue, return |
Exit a block or function early |
if and else¶
The condition in parentheses must produce a bool (or something convertible to one). If it is true, the block runs; otherwise it is skipped.
To handle the other case:
For more than two outcomes, chain with else if:
if (temperature > 80) {
std::cout << "Too hot\n";
} else if (temperature < 10) {
std::cout << "Too cold\n";
} else {
std::cout << "Fine\n";
}
Only the first matching branch runs. Once a branch is taken, the rest are skipped.
Use braces even for single-statement bodies. It is one extra line and avoids a surprising class of bugs when someone adds a second statement later.
switch¶
When you are comparing one value against several constants, switch is clearer than a long else if chain:
switch (gear) {
case 1: std::cout << "First\n"; break;
case 2: std::cout << "Second\n"; break;
case 3: std::cout << "Third\n"; break;
default: std::cout << "Unknown\n";
}
Two things to know:
- Always include
breakat the end of each case unless you specifically want execution to fall through to the next case. Forgettingbreakis a classic bug: execution silently continues into the next case. switchonly works with integer-like values (int,char, enumerations). It cannot switch on astd::stringor adouble.
A subtle trap: all the cases share one scope — the single block after switch (...). So a variable declared in one case is still in scope in the cases below it, and C++ forbids jumping over its initialisation. This innocent-looking code does not compile:
switch (gear) {
case 1:
int chosen = gear * 10; // declared here
std::cout << chosen << "\n";
break;
case 2: // jumping here would skip
std::cout << "Second\n"; // the line that sets up 'chosen'
break;
}
The compiler rejects it with something like "jump to case label crosses initialization of 'int chosen'": reaching case 2 would bypass the line that sets up chosen, yet chosen is still in scope there, so the language refuses.
Give the case its own scope with braces {}, and the variable lives and dies inside them:
switch (gear) {
case 1: {
int chosen = gear * 10;
std::cout << chosen << "\n";
break;
}
case 2:
std::cout << "Second\n";
break;
}
Now chosen exists only between the braces, so nothing leaks into case 2. Rule of thumb: the moment a case declares a variable, wrap that case in {}.
while¶
Repeat a block as long as a condition is true:
int countdown = 5;
while (countdown > 0) {
std::cout << countdown << "...\n";
--countdown;
}
std::cout << "Go!\n";
The condition is checked before each iteration. If it is false at the start, the body runs zero times.
The number-one bug with while loops is forgetting to make progress toward the exit condition:
If your program hangs, this is the first place to look.
do-while¶
Like while, but the condition is checked after the first iteration. The body therefore always runs at least once:
int input = 0;
do {
std::cout << "Enter a positive number: ";
std::cin >> input;
} while (input <= 0);
Use this when the work must happen before you know whether to continue. Common pattern: "read input until the user provides something valid."
for¶
When you know how many times to loop, for is the cleanest form:
The three parts inside the parentheses are:
- Initialisation (
int i = 0): runs once, before the loop starts. - Condition (
i < 5): checked before each iteration. Loop ends when false. - Update (
++i): runs after each iteration.
A for loop is just a while loop with the parts arranged for visibility. Use it whenever you have a counter.
Range-based for¶
For visiting every element of a container, the range-based for is shorter and harder to get wrong than a counter-based for:
std::vector<int> readings{42, 17, 99, 8};
for (int value : readings) {
std::cout << value << "\n";
}
If you do not need to modify the elements, prefer const auto& to avoid copying:
To modify the elements in place, take a non-const reference:
The & here makes value be the element itself rather than a copy of it: without it, each value is a fresh copy and changes to it are thrown away at the end of the iteration; with it, writing to value writes straight into the vector. (const on top of that promises you will only read, never write.) References are covered properly in Values, References, and Pointers; here you only need to know the & avoids the copy and lets you edit in place.
break, continue, return¶
These three change the flow inside a loop or function.
for (int i = 0; i < 100; ++i) {
if (i == 10) {
break; // exit the loop entirely
}
if (i % 2 == 0) {
continue; // skip the rest of this iteration, go to the next
}
std::cout << i << "\n";
}
breakexits the innermost loop orswitch.continueskips the rest of the current iteration and moves to the next.returnexits the function entirely (and optionally returns a value).
Choosing the right tool¶
| Situation | Use |
|---|---|
| Two or three branches based on a condition | if / else if / else |
| Many branches on one integer-like value | switch |
| Repeat until a condition becomes false | while |
| Loop body must run at least once | do-while |
| Fixed number of iterations with a counter | for |
| Visit every element of a container | range-based for |
| Exit a loop early | break |
| Skip to the next iteration | continue |