Why this matters
Many programs do not know in advance how many values they will receive. A cashier's till adds up items until the cashier presses a total key. A sensor logs readings until someone switches it off. A text file holds as many lines as it holds. The program needs a way to stop that does not depend on a count given at the start.
A sentinel is a special value that marks the end of the data. The loop reads values one at a time, and it stops when it reads the sentinel. The sentinel is not data. It is a stop signal, so it must be a value that real data never has.
This lesson covers loops that stop on a sentinel, including the sentinel that means "end of input". The basic while loop is in the first lesson of this module, and the counting loops that use a fixed number of passes are in the lessons before this one. Input from a menu of choices is a separate lesson.
The idea
Take a list of exam scores, all 0 or more. The value -1 cannot be a real score, so it is a good sentinel. The program reads scores until it reads -1, and then it prints the total and the average.
Two rules make this work:
- The sentinel must be a value that the real data can never have.
- The check must happen before the sentinel is used as data.
A loop that checks for the sentinel needs a value to check. So the program reads the first value before the loop starts. This is called a priming read, because it primes the loop with its first value. Inside the loop, the program handles the value it has, then reads the next value at the end of the body. The next check then looks at that new value.
Here is the order of events for the input 72, 85, 90, -1:
| Step | What happens | Value in score |
|---|---|---|
| 1 | Read before the loop | 72 |
| 2 | Check: 72 is not -1, so enter the body and add 72 | 72 |
| 3 | End of body: read the next value | 85 |
| 4 | Check: 85 is not -1, so add 85 | 85 |
| 5 | End of body: read the next value | 90 |
| 6 | Check: 90 is not -1, so add 90 | 90 |
| 7 | End of body: read the next value | -1 |
| 8 | Check: -1 is the sentinel, so the loop stops | -1 |
The body runs three times. The sentinel is read and checked, but it is never added.
Worked example 1: totals until -1
Here is a complete program that reads scores until it sees -1:
#include <iostream>
int main() {
const int sentinel = -1;
int score = 0;
std::cin >> score;
int total = 0;
int count = 0;
while (score != sentinel) {
total += score;
count += 1;
std::cin >> score;
}
std::cout << "count: " << count << '\n';
std::cout << "total: " << total << '\n';
if (count == 0) {
std::cout << "no scores entered\n";
} else {
std::cout << "average: " << total / count << '\n';
}
}The program reads its input one value per line. For the input 72, 85, 90 and -1, typed on four lines, the output is:
count: 3
total: 247
average: 82Walk through the code from the top. sentinel is a const int, so the program cannot change it by mistake. std::cin is the input stream, the place where keyboard input arrives, and >> reads one value from it into a variable. The first std::cin >> score is the priming read.
Inside the loop, total += score adds the current score, and count += 1 counts it. The second std::cin >> score at the end of the body is the read that moves the loop forward. Without it, the loop would look at the same value forever.
After the loop, the program prints the count and the total. The average is 82, not 82.33, because total / count divides two whole numbers, and C++ drops the fraction. The floating-point loop drift lesson covers decimal numbers.
The if (count == 0) test protects the division. If the first value is already -1, the body never runs and count stays 0. Dividing by 0 is a bug, and the mistakes section shows what happens.
With the input -1 alone, the output is:
count: 0
total: 0
no scores enteredWorked example 2: a sentinel that is not a number
A sentinel can be any type. This program reads words until it reads the word quit:
#include <iostream>
#include <string>
int main() {
std::string word;
std::cin >> word;
int count = 0;
while (word != "quit") {
count += 1;
std::cout << "you typed " << word << '\n';
std::cin >> word;
}
std::cout << "words typed before quit: " << count << '\n';
}Here word is a std::string, which holds text. The comparison word != "quit" checks whether the text is the same as the word quit. For the input hi, there, quit, typed on three lines, the output is:
you typed hi
you typed there
words typed before quit: 2The sentinel quit is never printed by the loop and never counted. It is only the signal to stop.
Worked example 3: the end of input as the sentinel
Sometimes there is no special value to type. The input simply ends. On a Linux or macOS terminal you end the input with Ctrl+D. Input from a file also ends at its last line. A stream that has no more data is said to have reached end of file, or EOF for short.
The read itself can serve as the check. The expression std::cin >> x gives back the stream, and the stream counts as true only while the last read worked. When the input ends, the read fails and the loop stops:
#include <iostream>
int main() {
// Reads numbers until end of input (Ctrl+D on a terminal, or the end of a file).
int x = 0;
int count = 0;
int total = 0;
while (std::cin >> x) {
total += x;
count += 1;
}
std::cout << "count: " << count << '\n';
std::cout << "total: " << total << '\n';
}With the input 4 5 on one line and 6 on the next, the output is:
count: 3
total: 15With no input at all, the output is count: 0 and total: 0. Here no priming read is needed, because the read is inside the condition. This form is useful when the input has no marker value. It does not work for a marker such as -1, because -1 is a successful read and the loop would need a separate check for it.
Worked example 4: the largest value until end of input
This loop reads numbers until the input ends and keeps the largest. It tracks whether it has seen any value yet:
#include <iostream>
int main() {
int x = 0;
int largest = 0;
int count = 0;
while (std::cin >> x) {
if (count == 0 || x > largest) {
largest = x;
}
count += 1;
}
if (count == 0) {
std::cout << "no numbers\n";
} else {
std::cout << "largest: " << largest << '\n';
}
}The test count == 0 is needed. If the first value is read, largest takes that value. Without this test, largest starts at 0, and a list of only negative numbers would give 0 as the largest, which is wrong. With the input 4 9 2, the output is largest: 9. With no input, the output is no numbers.
Common mistakes
1. Adding the sentinel to the total
A common version of this loop puts the read at the top and the test at the bottom. That version reads the sentinel, adds it, and only then checks it:
#include <iostream>
int main() {
// Mistake: the sentinel is added to the total before the check runs.
const int sentinel = -1;
int score = 0;
int total = 0;
do {
std::cin >> score;
total += score;
} while (score != sentinel);
std::cout << "total: " << total << '\n';
}With the input 5, 7 and -1, the output is:
total: 11The correct total is 12. The -1 was added before the check saw it. The fix is the priming read from the idea section: read first, check second, and add only inside the body.
2. Forgetting to read again
If the body does not read a new value, the loop checks the same value on every pass:
#include <iostream>
int main() {
// Mistake: the body never reads again, so the value never changes.
const int sentinel = -1;
int score = 0;
std::cin >> score;
while (score != sentinel) {
std::cout << "still " << score << '\n';
}
std::cout << "finished\n";
}The loop never ends. With the input 5, the program prints the same line again and again. The output below was cut off after three lines with head -3, because otherwise it would not stop:
still 5
still 5
still 5To stop the program, press Ctrl+C in the terminal. The fix is a read at the end of the body, as in worked example 1.
3. A bad value stops the reading but leaves the stream broken
When the input does not match the type of the variable, the read fails. For example, the letter x cannot be read as an int. After a failed read, the stream stays in a failed state, and later reads fail too. The sentinel is never seen, because each read fails before it can produce a value.
#include <iostream>
int main() {
// Mistake: no check that the read succeeded. A letter where a number is
// expected sets the stream's fail state, and every later read fails too.
const int sentinel = -1;
int x = 0;
std::cin >> x;
int passes = 0;
while (x != sentinel) {
passes += 1;
if (passes > 3) {
std::cout << "passes: " << passes << ", x is still " << x << '\n';
break;
}
std::cin >> x;
}
std::cout << "done\n";
}With the input 3 x 7 -1, the output is:
passes: 4, x is still 0
doneThe program reads 3, then fails on x. Since C++11, a failed read of an int stores 0 in the variable, so x becomes 0 and is never -1. The break in this demonstration stops the loop after four passes. Without it, the loop would run forever. The fix is a loop that stops when the read fails, as in worked example 3, or a check that the read worked before the loop continues. Recovering from bad input is a larger topic, and this lesson only shows the danger.
4. Dividing by zero when nothing was read
This version has no check for an empty list:
#include <iostream>
int main() {
// Mistake: no check for an empty loop, so count stays 0.
const int sentinel = -1;
int score = 0;
int total = 0;
int count = 0;
std::cin >> score;
while (score != sentinel) {
total += score;
count += 1;
std::cin >> score;
}
std::cout << "average: " << total / count << '\n';
}With the input -1 alone, the program stops with no output, and the shell reports exit code 136:
exit 136Exit code 136 is 128 plus 8. The number 8 is the signal for a floating-point exception, which is how this Linux machine reports integer division by zero. The fix is the count == 0 test from worked example 1.
5. A sentinel that can also be real data
A sentinel must be a value that real data cannot have. If the data can contain it, the loop stops too early. Suppose a quiz gives a score of 0 to a student who gets nothing right, and the program uses 0 as the sentinel:
#include <iostream>
int main() {
int x = 0;
int total = 0;
std::cin >> x;
while (x != 0) {
total += x;
std::cin >> x;
}
std::cout << "total: " << total << '\n';
}With the input 5, 0 and 7, followed by 0 to end, the output is:
total: 5The program stopped at the first 0 and never added 7. Choose a sentinel that is impossible in the data, such as -1 for scores that are never negative, and document why it was chosen.
Practice
Exercise 1
Read integers until the user enters 0. Print their total. Use 0 as the sentinel, and do not add it.
Show the answer
The loop starts with a priming read, then adds each value and reads the next one at the end of the body. The sentinel 0 is not added.
#include <iostream>
int main() {
int x = 0;
int total = 0;
std::cin >> x;
while (x != 0) {
total += x;
std::cin >> x;
}
std::cout << "total: " << total << '\n';
}With the input 5, 7 and 0, typed on three lines, the output is:
total: 12Exercise 2
Read words until the word end is typed. Print how many words came before it. The word end is not counted.
Show the answer
The loop has the same shape as worked example 2, but it counts the words and does not print them.
#include <iostream>
#include <string>
int main() {
std::string word;
int count = 0;
std::cin >> word;
while (word != "end") {
count += 1;
std::cin >> word;
}
std::cout << "words before end: " << count << '\n';
}With the input a, b and end, the output is:
words before end: 2Exercise 3
Read integers until the end of input. Print the largest one. If there were no numbers, print no numbers.
Show the answer
This uses the while (std::cin >> x) form from worked example 3, with the count == 0 test from worked example 4.
#include <iostream>
int main() {
int x = 0;
int largest = 0;
int count = 0;
while (std::cin >> x) {
if (count == 0 || x > largest) {
largest = x;
}
count += 1;
}
if (count == 0) {
std::cout << "no numbers\n";
} else {
std::cout << "largest: " << largest << '\n';
}
}With the input 4 9 2, the output is largest: 9. With no input, the output is no numbers.
Exercise 4
This loop is meant to add the numbers until -1 is read, but it never stops. Find the problem and fix it. Show the output for the input 4, 6 and -1.
#include <iostream>
int main() {
int x = 0;
int total = 0;
std::cin >> x;
while (x != -1) {
total += x;
}
std::cout << "total: " << total << '\n';
}Show the answer
The body never reads a new value, so x stays 4 and the loop runs forever. The fix is to read at the end of the body.
#include <iostream>
int main() {
int x = 0;
int total = 0;
std::cin >> x;
while (x != -1) {
total += x;
std::cin >> x;
}
std::cout << "total: " << total << '\n';
}Output for the input 4, 6 and -1:
total: 10Exercise 5
Read temperatures in degrees Celsius until -273 is entered. Print their average with two decimal places. If no temperatures were read, print no readings.
Show the answer
The values are double, so the average can have a fraction. The sentinel -273 is a whole number, which a double holds exactly, so comparing it with != is safe here. The count == 0 test handles the empty case. The std::cin check also stops the loop if the input ends before -273.
#include <iomanip>
#include <iostream>
int main() {
const double sentinel = -273;
double t = 0.0;
double total = 0.0;
int count = 0;
std::cin >> t;
while (std::cin && t != sentinel) {
total += t;
count += 1;
std::cin >> t;
}
if (count == 0) {
std::cout << "no readings\n";
} else {
std::cout << std::fixed << std::setprecision(2)
<< "average: " << total / count << '\n';
}
}With the input 20, 25 and -273, the output is:
average: 22.50With -273 alone, the output is no readings.
Summary
- A sentinel is a value real data cannot have, and the loop stops when it reads it.
- Read the first value before the loop, check it in the condition, and read the next value at the end of the body.
- A loop that reads the input in its condition, such as
while (std::cin >> x), stops when the input ends. - Check the count before you divide by it, because an empty input gives a count of 0.
- Make sure the sentinel is never added to the total and never mistaken for data.