How do you handle problems where the loop reads data from the user until some special value, or sentinel, is found to signal the end of the input? This is called a sentinel loop and its logical structure is:
Read a value If the value is equal to the sentinel then Exit the loop Process the value
There is no easy test at the beginning of the loop; you don't know when the sentinel is encountered until you've read a value. There are three ways to rearrange the statements to handle this situation.
The Primed Loop Pattern
The primed-loop pattern is named after the old-west water pump which required users to pour water down the well to establish suction, before pumping began.
Here's what that looks like in pseudocode:
Prompt user and read in a value While the value is not the sentinel Process the data value Prompt user and read next value
This is the classic way to process sentinel data. The code used to read each data value is duplicated, before the loop and at the end of the loop.
Here's a primed-sentinel-loop that sums a sequence, using 0 as the sentinel:
cout << "Add integers. Enter 0 when done." << endl;
int total = 0, value = -1;
cout << "> ";
cin >> value; // Read before the loop
while (value != 0) // Check for the sentinel
{
total += value; // No sentinel? Process
cout << "> "; // Prompt and read next item
cin >> value;
}
cout << "Total: " << total << endl;
The Loop-and-a-half Pattern
The loop-and-a-half is a kind of loop that is available in some language (like Ada's Exit When), but which must be simulated in C and C++, by using if and break. This is another way to write a sentinel loop.
- Write an endless loop (or a while loop with a necessary condition).
- Add in if statement inside the loop which checks the sentinel.
- If you find the sentinel, use break, which has the effect of immediately terminating the nearest enclosing loop.
The loop-and-a-half pattern has the advantage that it follows the natural structure: the read-until-sentinel pattern:
While True Prompt user and read value If value is the sentinel then break out of the loop Process the value
Note that this is an endless loop, where the only way to exit is by executing the break statement. Here's the same problem as on the previous page, using the loop-an-a-half pattern. You may want to look back and compare them.
while (true) // Endless loop
{
cout << "> "; // Prompt and read item
cin >> value;
if (value == 0) { break; } // Sentinel? Leave loop
total += value; // No sentinel? Process
}
The Flag-controlled Pattern
A third way to implement the read-until-sentinel pattern is to use a flag-controlled loop, where you introduce an additional Boolean variable just before the loop starts and set it to false. Inside the loop you read a data value and check the sentinel, just as in the loop-and-a-half.
Instead of a break statement, set your flag variable to true when the sentinel is read. Otherwise, you process that data value as normal:
Set finished to false // Boolean control flag while not finished read the value if value is the sentinel then set finished to true else process the variable
As we've done with the other two methods, here is the same program implemented as a flag-controlled sentinel loop:
bool finished; // control flag
while (! finished)
{
cout << "> "; // Prompt and read item
cin >> value;
if (value == 0)
{
finished = true;
}
else
{
total += value;
}
}
Which of these thre versions should you use? Eric Roberts, a professor at Stanford for many years, suggests that empirical studies demonstrate that students are more likely to write correct programs if they use the loop-and-a-half version than if they are forced to use some other strategy. If you're interested, follow the link to read Roberts' paper.
Validating Data
When you read a value from cin, it is possible that the input may fail because the user entered invalid data. For instance:
cout << "Enter a number: ";
int n;
cin >> n;
cout << n << endl;
Suppose that the user types in one when asked to enter a number. Here's what happens:
- The cin object enters a failed state and will stop accepting any more input.
- The variable n will be set to 0.
- Call cin.clear() to allow cin to start accepting data once again.
- Consume the bad data by creating a string variable and reading it.
You can check for success by calling the member function fail() or by simply using a regular if statement. Here's a fragment that shows how to use if:
int n;
if (cin >> n) { cout << n << endl };
else { cout << "Invalid input" << endl; }
And, here's a fragment which explicitly calles the fail() member function:
int n;
cin >> n;
if (cin.fail()) { cout << "Invalid input" << endl };
else { cout << n << endl;}
Recovering
Inside a sentinel loop, you'd like to recover if the user inadvertently entered bad data.
while (true) // Endless loop
{
cout << "> "; // Prompt and read item
if (cin >> value) {
if (value == 0) {break; { // Sentinel? Leave loop
total += value; // No sentinel? Process
{
else {
cin.clear(); // Clear the fail flag
string bad_data; // store the bad data
in >> bad_data; // read it and ignore it
{
{