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:
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:
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 |