Summary
A program that merely chains statements always does the same thing, whatever its data; for it to respond to them, it must decide. This lesson studies the decisions of C++: if and else, which choose between two paths according to a condition; chains of else if, which choose among several, and in which the order of the conditions decides which case each branch handles; nested decisions and the trap of the dangling else; and switch with enum class, which choose among named discrete values, with a compiler that warns if a case is missing or if one falls through into the next. The concept that unifies it all is case analysis: a correct decision partitions the set of inputs into disjoint cases that cover it completely, and each case is checked with one test in its interior and another at each of its boundaries.
Learning objectives
- Write simple, chained and nested decisions that handle every case of a specification.
- Verify that the cases of a chain of
ifandelse ifare disjoint and exhaustive, and design one test input per case. - Use
switchwithenum classfor discrete values, and explain what the compiler warns about if a case is missing. - Detect the dangling
elseand fallthrough between the cases of aswitch, and fix them.
if and else
The form of a decision
The lesson From the problem to the algorithm presented three forms from which every algorithm is built: sequence, decision and repetition. Until now the programs of the course have been sequences, apart from an occasional if used ahead of time in unit 1; this lesson writes the second form in C++. A decision evaluates a condition, that is, an expression of type bool like those of the lesson Relations and logic, and executes one block if the condition is true and another if it is false:
if (condition) { // runs if the condition is true} else { // runs if the condition is false}The condition always goes in parentheses. The else part is optional: without it, if the condition is false nothing is done and the program continues with the statement after the decision. In both cases the two paths join at the end, so that the decision, seen from outside, is a single statement with one entry and one exit, as lesson 1.1 required.
The lesson Arithmetic showed that a % b has the sign of the dividend, so that with and it equals , whereas the Euclidean remainder, which is never negative, equals 1; and it announced that the safe way to correct it is to add only when the remainder is negative. The program assumes .
#include <iostream>#include <print>int main(){ int a{}; int b{}; std::cin >> a >> b; int r{a % b}; if (r < 0) { r = r + b; } std::println("Euclidean remainder of {} divided by {}: {}", a, b, r);}-7 2Euclidean remainder of -7 divided by 2: 1The variable r is no longer constant, because the decision may change it. The figure follows the two possible paths.
Proof
Execution reaches the decision with
ralready computed, and the first thing it does is evaluate the conditionr < 0: the diamond is the only point of the program where the paths separate.If the condition is true, as with and 2, the block of the
ifis executed:rgoes from to 1.If it is false, as with 7 and 2, the block is skipped entirely, and
rkeeps its value.Both paths join at the same point, and from there the program continues in the same way in both cases: the statement that writes the result is always executed.
With else, the decision chooses between two actions that exclude each other. The leap-year condition of the lesson Relations and logic produces a bool; a decision turns it into a behavior of the program.
#include <iostream>#include <print>int main(){ int year{}; std::cin >> year; const bool isLeap{(year % 4 == 0 && year % 100 != 0) || year % 400 == 0}; if (isLeap) { std::println("{} is a leap year: February has 29 days.", year); } else { std::println("{} is not a leap year: February has 28 days.", year); }}19001900 is not a leap year: February has 28 days.Giving the condition a name, such as isLeap, separates what is decided from what is done with the decision.
Braces always
The grammar of C++ allows the braces to be omitted when the block has a single statement: if (r < 0) r = r + b; is valid. Now, without braces the decision governs only the next statement, and what the reader believes to be governed depends on the indentation, which the compiler ignores. If this is so, any later modification (adding a second statement, commenting out the first) can change the meaning without the text showing it. From this follows the rule of the course: the block of an if or of an else always has braces, even if it contains a single statement.
The shortest error of all confirms the rule. A semicolon after the parentheses is an empty statement, and the decision governs that empty statement; the block that follows no longer belongs to the if and is always executed.
#include <print>int main(){ const int stock{12}; if (stock == 0); { std::println("Out of stock"); } std::println("Units available: {}", stock);}main.cpp: In function ‘int main()’:main.cpp:6:20: warning: suggest braces around empty body in an ‘if’ statement [-Wempty-body] 6 | if (stock == 0); | ^main.cpp:6:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation] 6 | if (stock == 0); | ^~main.cpp:7:5: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’ 7 | { | ^Out of stockUnits available: 12The program announces that the product is out of stock although 12 units remain. GCC gives two warnings: -Wempty-body suggests braces around the empty body, and -Wmisleading-indentation points out that the block is indented as if the if governed it. The other classic error, if (x = 0) instead of if (x == 0), was seen in the lesson Compiling, running and configuring: the assignment equals zero, which is converted to false, and -Wparentheses warns. In both cases the program compiles; without the warnings, the error would go unnoticed.
The braces have one more consequence: a variable declared inside the block of an if exists only there. If a value computed in a branch is used after the decision, the variable is declared before it, like r in the remainder example; scope will be studied in the lesson Parameters and scope.
The conditional operator
When the two branches of a decision only choose a value, C++ offers a shorter form: condition ? a : b equals a if the condition is true and b if it is false, and evaluates only the chosen element. Unlike if, which is a statement, the conditional operator is an expression: it has a value and a type, and it can go wherever any expression goes, for example in the initialization of a constant, which an if would not allow without giving up const.
#include <iostream>#include <print>int main(){ int a{}; int b{}; std::cin >> a >> b; const int larger{a > b ? a : b}; std::println("Larger: {}", larger); std::println("{} {}", b, b == 1 ? "unit" : "units"); const auto mixed{a > b ? a : 0.5}; std::println("Common type: {:.1f}", mixed); std::println("Without parentheses: {}", 10 + a > b ? 1 : 0); std::println("With parentheses: {}", 10 + (a > b ? 1 : 0));}7 3Larger: 73 unitsCommon type: 7.0Without parentheses: 1With parentheses: 11The first two lines show the use in which it clarifies: choosing the larger value and making a word agree with the number, without repeating the whole statement in two branches. The third shows that the expression has a single type, whichever branch is chosen: if the two results are of different types, they are converted to a common type by the usual arithmetic conversions of the lesson Arithmetic; here, an int and a double give double, and the chosen 7 is stored as 7.0. The last two show precedence: the conditional operator is among those of lowest priority, so that without parentheses the condition is 10 + a > b and the value, 1, instead of 11. When the conditional is part of a larger expression, it goes in parentheses.
The operator obscures as soon as it is asked for more than choosing a value. Nested, as in a > b ? (a > c ? a : c) : (b > c ? b : c), it forces the reader to rebuild mentally a nested decision without the braces that make it readable; and used for its effects, as in ok ? std::println("yes") : std::println("no"), it is a disguised if. The rule of the course is to use it only to choose between two short values, without nesting it; everything else is written with if.
Loading the content…
Completed
Sign in to save your progress