Decisions
Every program so far ran the same statements each time, from the top to the bottom. This lesson shows how a program asks a question about its values and then runs some statements and skips others, depending on the answer.
You need what modules 1 to 3 gave you: variables, std::cout, std::cin, and the remainder %.
Questions with a yes or no answer
A comparison puts two values side by side and asks one question about them. The answer is one of two values, true or false.
#include <iostream>
int main() {
int price = 12;
int budget = 20;
std::cout << (price < budget) << "\n";
std::cout << (price == budget) << "\n";
bool can_buy = price <= budget;
std::cout << can_buy << "\n";
}
1
0
1
price < budget asks "is 12 less than 20?". The answer is true. std::cout prints true as 1 and false as 0. It does not print the words.
There are six comparisons.
| You write | It asks |
|---|---|
a < b |
is a less than b? |
a > b |
is a greater than b? |
a <= b |
is a less than b, or equal to it? |
a >= b |
is a greater than b, or equal to it? |
a == b |
are a and b equal? |
a != b |
are a and b different? |
Three things to notice.
- "Equal" is written with two equals signs,
==. One equals sign is assignment, which you know from module 2: it stores a value. One = too few shows what happens when the two are confused. <=and>=include the case where the two values are equal.<and>do not. When both values are 20,price <= budgetistrueandprice < budgetisfalse.- A comparison printed in a
<<chain needs parentheses around it.<<is worked out before<, so without them the compiler would try to printpricefirst and compare afterwards, and it rejects that.
true and false are values of a type called bool. A bool variable holds one of those two values and nothing else. The line bool can_buy = price <= budget; works out the comparison and stores its answer, in the same way that int total = 3 + 4; works out the sum and stores 7. No parentheses are needed there, because = is worked out last.
if
An if statement runs some statements only when the answer to a question is true.
#include <iostream>
int main() {
int stock = 3;
if (stock < 5) {
std::cout << "Order more.\n";
}
std::cout << "Stock: " << stock << "\n";
}
Order more.
Stock: 3
Its parts, in order:
- the word
if; - a condition in parentheses: something that works out to
trueorfalse, such as a comparison; - braces
{and}around the statements that the condition controls. The statements between a pair of braces are called a block.
The computer works out the condition. If it is true, the block runs. If it is false, the whole block is skipped.
The last statement is after the closing brace, so the if does not control it. It runs either way. Change stock to 8 and the condition is false: the first message is skipped, and the Stock: line is still printed.
There is no semicolon after the parentheses or after the closing brace. The statements inside the block are indented, so that a reader can see at a glance what the if controls. The compiler does not need the indentation. The reader does.
else
Often there are two things to do: one when the condition is true, another when it is false. An else adds the second block.
#include <iostream>
int main() {
int balance = 40;
int price = 0;
std::cin >> price;
if (price <= balance) {
std::cout << "Paid. Left: " << balance - price << "\n";
} else {
std::cout << "Not enough money.\n";
}
std::cout << "Goodbye.\n";
}
55
Not enough money.
Goodbye.
The condition 55 <= 40 is false, so the first block is skipped and the block after else runs.
Exactly one of the two blocks runs, every time. Never both, never neither. Then the program continues after the last closing brace, with Goodbye.
An else has no condition of its own. It means "in every other case". It belongs to the if whose block it follows.
A chain of choices
For more than two cases, put another if after the else. This is a chain.
#include <iostream>
int main() {
int age = 0;
std::cin >> age;
if (age < 5) {
std::cout << "Free\n";
} else if (age < 18) {
std::cout << "Child ticket\n";
} else if (age < 65) {
std::cout << "Adult ticket\n";
} else {
std::cout << "Senior ticket\n";
}
}
18
Adult ticket
The conditions are tested from the top, one at a time. The block of the first condition that is true runs. Everything below it in the chain is skipped, without being tested. If no condition is true, the final else block runs.
Follow it for 18. 18 < 5 is false. 18 < 18 is false too, because 18 is not less than itself. 18 < 65 is true, so Adult ticket is printed and the chain is finished.
Three things to notice.
- Each test can rely on the ones above it having failed. When the computer reaches
age < 18, it already knows thatage < 5wasfalse. So the second block is for ages 5 to 17, although the condition does not mention 5. - The order matters. If
age < 65were the first test, a child of 3 would get an adult ticket, because 3 is less than 65 and the firsttruecondition wins. Put the narrowest test first. - The numbers in the conditions are boundaries. Most mistakes in a chain are at a boundary:
<written where<=was meant, or the other way round. Try the value on each side of every boundary. Here that means 4 and 5, 17 and 18, 64 and 65.
A chain is not the same as several separate if statements. Without the word else, every if is a question of its own, and each is asked whatever the answers to the others were. Two of them can be true for the same value, and then two blocks run. In a chain, at most one block runs.
One = too few
== compares. = assigns. Writing one = inside a condition is a mistake that the compiler accepts.
#include <iostream>
int main() {
int seats = 3;
if (seats = 10) {
std::cout << "The bus is full.\n";
}
std::cout << "Seats taken: " << seats << "\n";
}
The bus is full.
Seats taken: 10
Three seats were taken, and the program says the bus is full. Read the condition as the computer does.
seats = 10is an assignment. It stores 10 inseats. The 3 is gone.- The condition needs a
trueor afalse, and it has been given the number 10. C++ turns a number into aboolby one rule: 0 counts asfalse, and every other number counts astrue. - 10 is not 0, so the block runs.
So this if does two things that were not intended. It changes the variable, and it does not ask a question at all. With seats = 0 in the condition the block would never run, whatever seats held before.
Most compilers, including the one in the lab, print a warning for a line like this. A warning is not an error: the program still runs. It is the compiler saying that the line is legal and probably not what you meant. Read warnings.
Both, either, not
Three more symbols join answers together.
| You write | Say | It is true when |
|---|---|---|
a && b |
a and b | both are true |
a \|\| b |
a or b | at least one is true |
!a |
not a | a is false |
Here is every case.
a |
b |
a && b |
a \|\| b |
!a |
|---|---|---|---|---|
false |
false |
false |
false |
true |
false |
true |
false |
true |
true |
true |
false |
false |
true |
false |
true |
true |
true |
true |
false |
Notice that || is true when both sides are true. In everyday English "tea or coffee" often means one and not both. In C++ "or" includes both.
#include <iostream>
int main() {
int hour = 14;
bool is_holiday = false;
bool is_open = hour >= 9 && hour < 17 && !is_holiday;
std::cout << is_open << "\n";
std::cout << (hour < 9 || hour >= 17) << "\n";
}
1
0
The shop is open from 9 up to, and not including, 17, except on holidays. At 14 on an ordinary day all three parts of is_open are true, so the whole condition is true. The second line asks whether the hour is outside opening time. Neither side is true, so the answer is false.
A range needs two comparisons joined by &&. In mathematics you would write 9 ≤ hour < 17. In C++ that form does not mean what it says.
std::cout << (9 <= hour < 17) << "\n";
By the rules of C++, the computer would work out 9 <= hour first and get true or false. Then it would compare that answer with 17. Used as a number, true counts as 1 and false as 0, and both are less than 17. So this condition would be true for every hour, including 23.
The compiler in this lab knows the mistake and refuses the program, with a message that says a chained comparison does not behave like a mathematical expression. Some other compilers accept the line and let the program give wrong answers. Either way, write the variable twice.
#include <iostream>
int main() {
int hour = 23;
std::cout << (9 <= hour && hour < 17) << "\n";
}
0
The order in which these are worked out: comparisons first, then !, then &&, then ||. That is why hour >= 9 && hour < 17 needs no parentheses. Two cases do need them.
- To apply
!to a comparison, put the comparison in parentheses:!(hour < 9). - When one condition mixes
&&and||, put parentheses around the part you mean to be worked out first.a || b && cmeansa || (b && c), which is rarely what a reader guesses.
Where a variable ends
A statement such as int discount = 10; creates a variable: it gives a type, a name and a first value. To create a variable in this way is to declare it, and the statement is its declaration.
A variable does not last for the whole program. A variable declared inside a block ends at that block's closing brace. After the brace, the name means nothing.
#include <iostream>
int main() {
int total = 120;
if (total > 100) {
int discount = 10;
}
std::cout << "Discount: " << discount << "\n";
}
The compiler rejects the last statement with a message like this one.
your_program.cpp:8:34: error: use of undeclared identifier 'discount'
It is the message you get for a misspelt name, and for the same reason: at line 8 there is nothing called discount. The variable began on line 6 and ended at the brace on line 7.
The part of a program in which a name can be used is called its scope. The scope of a variable starts at its declaration and stops at the closing brace of the block it was declared in.
When a value is needed after an if, declare the variable before the if, and give it a new value inside.
#include <iostream>
int main() {
int total = 120;
int discount = 0;
if (total > 100) {
discount = 10;
}
std::cout << "Discount: " << discount << "\n";
}
Discount: 10
Now discount is declared in the block of main, so it lasts until the final brace. A block can use the variables of the block around it, which is how the statement inside the if reaches it.
Inside the braces the line is discount = 10;, an assignment, with no type in front. Writing int discount = 10; there would declare a second variable with the same name. That second one would get the 10 and end at the brace, and the first would still hold 0.
switch
A chain that compares one whole number with several fixed values can be written as a switch.
#include <iostream>
int main() {
int place = 2;
switch (place) {
case 1:
std::cout << "Gold\n";
break;
case 2:
std::cout << "Silver\n";
break;
case 3:
std::cout << "Bronze\n";
break;
default:
std::cout << "No medal\n";
}
std::cout << "Thank you for taking part.\n";
}
Silver
Thank you for taking part.
Read it in this order.
switch (place)works out the value in the parentheses once. It must be a whole number.- Each
casenames one fixed value and ends with a colon. The computer jumps to thecasewhose value is equal to the one it worked out, and runs the statements from there. break;leaves theswitch. The program continues after its closing brace.default:is where the computer jumps when nocasematches. It plays the part of the finalelse.
A case is a place to jump to. It is not a block, and nothing stops the computer at the next case line. Only break does. This is the same program with one break removed.
#include <iostream>
int main() {
int place = 2;
switch (place) {
case 1:
std::cout << "Gold\n";
break;
case 2:
std::cout << "Silver\n";
case 3:
std::cout << "Bronze\n";
break;
default:
std::cout << "No medal\n";
}
}
Silver
Bronze
The computer jumped to case 2: and printed Silver. No break followed, so it ran on into the statements written under case 3: and printed Bronze as well. The break after that stopped it. The compiler accepts this. End every case with break;.
A switch tests only "is this value equal to that fixed value?". For a range, such as age < 18, or for a condition with &&, use a chain of else if.
Did the read work?
Module 3 said that std::cin >> age; fails when the input is not a number, and left it there. The read can be checked, because std::cin >> age can itself be used as a condition. It is true when a value was read and stored, and false when the read failed.
#include <iostream>
int main() {
int age = 0;
if (std::cin >> age) {
std::cout << "Next year you will be " << age + 1 << ".\n";
} else {
std::cout << "I need a whole number.\n";
}
}
41
Next year you will be 42.
The condition does two jobs. It tries to read, and it answers whether that worked. Here the input was 41, the read worked, and the first block ran.
Put soon in the Input box and run it again. The text soon cannot be stored in an int, so the read fails, the condition is false, and the else block runs.
Use age only in the block where the read worked. After a failed read, do not use the variable: what it holds is not what the user typed.
From now on, a program that reads a number and then depends on it should check the read in this way.