Why would you ever want to repeat something? Perhaps the user has many similar tasks and you'd simply like to code that once and tell the computer to repeat it for the number of tasks the user has. Perhaps your user is indecisive and needs multiple chances to make the proper entry/choice. Perhaps you just want to trap the user in a mindless repetition forevermore!
Oh, sorry, got carried away there...
But, seriously, there are many situations when you'll want your program to repeat something. Let's start with probably the most basic (useful) example:
Enter your numbers to add: 4 12 16 5 8 10 7 3...
If we know in advance how many numbers the user will add, we can make a separate variable for each one and make a cin statement such as:
cin >> v1 >> v2 >> v3 ...;
But that doesn't make the program very re-usable. A second option is to notice that the sum of a set of numbers can be done in any order:
4 + 12 + 16 + 5 + 8 + 10 + 7 + 3 (4 + 12) + (16 + 5) + (8 + 10) + (7 + 3) 4 + (12 + 16) + (5 + 8) + (10 + 7) + 3 etc.
Also, adding 0 to the set changes nothing:
0 + 4 + 12 + 16 + 5 + 8 + 10 + 7 + 3 0 + (4 + 12) + (16 + 5) + (8 + 10) + (7 + 3) 0 + 4 + (12 + 16) + (5 + 8) + (10 + 7) + 3 etc.
Combining these two ideas we come up with:
((((((((0 + 4) + 12) + 16) + 5) + 8) + 10) + 7) + 3)
Or a sequence of summed pairs starting with 0 and their first value. Then that sum and their next value. And so on. What we have is a series of additions like:
s = 0; cin >> n; s = s + n; cin >> n; s = s + n; cin >> n; s = s + n; cin >> n; s = s + n; cin >> n; s = s + n; ...
Now we only need two variables to add any length sequence. But, once again, when do we stop? There are several ways to answer this and they lead to the questions we'll need to answer to decide what sort of looping structure we are going to use in our program.
If the answer is yes, we can use a definite loop -- one that loops a definite (known, defined) number of times. C++ actually has only one definite style loop, so this makes our choice a snap. We'll be using a for loop.
If the answer is no, we must continue the questioning:
If the answer is yes, we can use an indefinite loop -- one that loops an indefinite (unknown, undefined) number of times. In particular, we'll use a loop that keeps going until a certain thing happens. This is known as a sentinel loop. In C++ we can create a sentinel loop in one of two ways: a while loop or a do loop. So, we go back to the questions:
This may at first seem a silly question. What sense would it make for there to be no numbers to add up? It actually happens all the time (later), but the most basic situation is the lost little user: Your user finds a program on their harddrive and has forgotten what it was; it's called x2.exe which is terribly un-useful in discovering its purpose; they click to run it and are greeted with a prompt to enter numbers to add -- but they have none!
Okay, so this is still silly, but I swear that later it will be useful to have a loop that may add no numbers (or something fairly similar). So, if they may have no numbers to add and we are stopping at a special value(s), we'll use a while loop. If they have at least one number to add and we are stopping at a sentinel value(s), we'll use a do loop.
The above questions are terribly specific to our summation example, however. If we wanted to know in a more general way which loop to use for a situation, we need questions to ask which are less specific and more general. Maybe questions like these:
If we answer YES, then we'll use a for loop.
If we answer no, we'll ask another question:
If we answer YES, then we'll use a do loop.
If we answer NO, then we'll use a while loop.
In general, it only takes two questions to decide amongst three alternatives, after all.
Before we move on, just a note on terminology. We already mentioned that the for loop is a definite style loop. We also mentioned that the do loop and while loop are both indefinite style loops. But there is another means of classification: when does the loop decide to continue or stop in relation to the actions the loop repeats?
If the continue/stop test is done before the actions (as in a for loop or a while loop), then the loop is said to be a pre-test loop (pre == before). If the continue/stop test is done after the actions (as in a do loop), then the loop is said to be a post-test loop (post == after).
...also priming and sentinel...
We now know how to classify loops by number of iterations and relative position of the continuance test. This tells us that there are at least two parts to a loop: actions and a test. We can also probably guess that the test will be a logical condition (bool). But that isn't all there is to a loop. There are actually four basic parts to any/all loops:
The body is the action(s) you are repeating -- if you have nothing to repeat, why are you repeating it?
The condition tells whether the loop should continue or stop. In C++, all conditions are true to continue the loop and false to stop the loop. This boolean condition is typically a test of some kind ('is less than', 'is equal to', etc.) based on a particular variable. This variable that helps to control when the loop will/will not keep going is known as the Loop Control Variable (LCV).
The initialization places values in the variable(s) used in the condition (the LCV's) so that the logical test even makes sense. (After all, it wouldn't make sense to ask whether or not x == 4 if we haven't given x a value yet! It makes as much sense as asking if the sky is a deck of playing cards...well, sorta.)
The update alters the values of the LCV's (the variable(s) used in the condition) so that the logical test will eventually make the loop stop (thet is, so that the test will become false -- in C++).
NOTE: More to come...
(See below...)
...for (init; cond; upd)...
for (init; cond; upd) { body }
...summation...
sum = 0; cin >> count; for (short n = 0; n != count; n++) { cin >> num; sum += num; }
...definite integral...
cin >> a >> b >> num_boxes; delta = (b-a)/num_boxes; x = a+delta/2; sum = 0; for (short box = 0; box != num_boxes; box++) { sum += delta*f(x); x += delta; }
...while (cond)...
init while (cond) { body upd }
...summation (numeric sentinel)...
sum = 0; cin >> num; while (num >= 0) { sum += num; cin >> num; }
...see also validation below...
...do ... while (cond);...
// often init do { body // often upd } while (cond);
...summation...
sum = 0; do { cin >> num; if (num >= 0) { sum += num; } } while (num >= 0);
...babysitting...
do { cin >> child_is; switch (toupper(child_is)) { case 'N': // naughty // punish break; case 'C': // cranky // put to bed break; case 'H': // hungry // feed break; default: // where's that kid?! // find him/her! break; } cin >> parents_home; } while (tolower(parents_home) != 'y');
...stop vs. continue...
...flag-men not just for highway construction...
...bool to the rescue...
...physical vs. virtual...
...fail/clear...
read while (!cin.fail()) { process read } cin.clear(); // clear failure cin.ignore(); // remove sentinel char // could also 'cin.ignore(numeric_limits<streamsize>::max(), '\n');' if we expect // they might have entered, for example, 'quit' instead of // just 'q'.
Also known as EOF. It is actually possible to type the special character symbol that the Operating System uses for its 'end-of-file' marker at the keyboard. Under DOS/Windoze, this symbol is Ctrl-Z (often shown as ^Z; also F6 with standard command prompt settings). Under *nix (including OS/X), this symbol is Ctrl-D (again, often shown as ^D).
HOWEVER, be ye hereby warned! When cin has read an EOF symbol, it typically goes so berserk that even its favorite counselor -- clear -- cannot heal its wounded soul! It can't seem to get rid of the EOF symbol from its buffer (no, it won't extract it, get it, or even ignore it), either!
In short, if you attempt to use an EOF termination loop -- of any kind, your program won't be doing ANY input there-after!!!
...eof...(seems to only work if typed after an <Enter>)
read while (!cin.eof()) // look for user to do ^Z (DOS/Win) or ^D (*nix) { process read } //cin.clear(); // we could clear end of 'file', but cin will never // be the same again, anyway. The EOF marker is stuck there and, it // would seem, cannot be removed. If you allow your user to use EOF // entry termination, beware!
...combining fail and eof...
read while (!cin.fail() && !cin.eof()) { process read } if (cin.fail()) { cin.clear(); // clear failure cin.ignore(); // remove sentinel char // could also 'cin.ignore(numeric_limits<streamsize>::max(), '\n');' if we expect // they might have entered, for example, 'quit' instead of // just 'q'. } else { //cin.clear(); // we could clear end of 'file', but cin will never // be the same again, anyway. The EOF marker is stuck there and, it // would seem, cannot be removed. If you allow your user to use EOF // entry termination, beware! }
OR even better...:
read while (cin.good()) // while nothing bad has happened... { process read } if (cin.fail()) { cin.clear(); // clear failure cin.ignore(); // remove sentinel char // could also 'cin.ignore(numeric_limits<streamsize>::max(), '\n');' if we expect // they might have entered, for example, 'quit' instead of // just 'q'. } else { //cin.clear(); // we could clear end of 'file', but cin will never // be the same again, anyway. The EOF marker is stuck there and, it // would seem, cannot be removed. If you allow your user to use EOF // entry termination, beware! }
...
A primed validation loop:
prompt read while (!valid) { error message prompt read }
For instance, to validate that the user's value is non-negative (a domain restriction that comes up quite often in real-world applications), we could do something like this:
double read_non_neg(const string & prompt, const string & errmsg) { double x; cout << prompt; cin >> x; while (x < 0) { cout << errmsg << prompt; cin >> x; } return x; }
...
Well, the above section made sure that data was within the required domain. But what if we aren't even sure our user is typing the correct kind of data? We can ask cin if it was happy with the user's entry and, when it isn't, make them re-enter!
read while (cin.fail()) { cin.clear(); cin.ignore(); // or cin.ignore(numeric_limits<streamsize>::max(), '\n'); // error message? re-prompt? read }
Of course, by its very nature, this sort of thing cannot be used with the non-numeric sentinel procedure above...
...
The error message/re-prompt question is actually a fairly subtle one. Consider this segment:
Enter age of client: forty-two
If you don't print an error message or re-prompt, the user just sees no progress. They think the program is working on something or 'thinking' itself... But, with an error message it might go something like this:
Enter age of client: forty-two
Invalid data! Try again: Invalid data! Try again: Invalid data! Try again: Invalid data! Try again: Invalid data! Try again: Invalid data! Try again: Invalid data! Try again: Invalid data! Try again: Invalid data! Try again:
That's kinda nasty... The error message is printed once for each character. If we switch to the numeric_limits<streamsize>::max() version of ignore, it is better:
Enter age of client: forty-two
Invalid data! Try again:
...
Although we can't combine the 'validity' validation with a non-numeric sentinel entry scheme, we can combine it with domain validation. One scenario is like this:
void read_actual_number(double & num) { cin >> num; while (cin.fail()) { cin.clear(); cin.ignore(); cin >> num; } return; } double read_non_neg(const string & prompt, const string & errmsg) { double x; cout << prompt; read_actual_number(x); while (x < 0) { cout << errmsg << prompt; read_actual_number(x); } return x; }
Again, the single char ignore, while certainly hopeful and optimistic of our user, is going to leave them blinking and wondering what is going on with no 'bad data' message. Let's try this instead:
void read_actual_number(double & num, const string & errmsg) { cin >> num; while (cin.fail()) { cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); cout << errmsg; cin >> num; } return; } double read_non_neg(const string & prompt, const string & domerrmsg, const string & daterrmsg) { double x; cout << prompt; read_actual_number(x, daterrmsg); while (x < 0) { cout << domerrmsg << prompt; read_actual_number(x, daterrmsg); } return x; }
This is going to behave much better! The only problem we might have is programmers who'll abuse our system so they don't have to do proper work. For instance, suppose Fred Glumquist is trying to use our validation library and is thinking we've used optimistic ignores. He might call our read_actual_number in a situation where he expects front-side units (like a '$' for money)... Fred will be quite surprised to find that the user's entire entered line is removed and they have to re-type their money without the '$' in front!
Let's give this one last go:
void read_actual_number(double & num, const string & errmsg) { cin >> num; // try to read a number while (cin.fail()) // eek! bad man entered ';'! { cin.clear(); // forget the failure...aaahhhh... cin.ignore(); // throw out THE char that offended >>... if (cin.peek() == '\n') // that was the end of the crap they entered { cout << errmsg; // error message once per line -- if needed } cin >> num; // re-try } return; } double read_non_neg(const string & prompt, const string & domerrmsg, const string & daterrmsg) { double x; cout << prompt; read_actual_number(x, daterrmsg); while (x < 0) { cout << domerrmsg << prompt; read_actual_number(x, daterrmsg); } return x; }
Cool! Now it will work like we wanted (one message per line FULL of crap) and how Fred wanted (ignore up to real data without a message).
But Jason, why's it still the 'that's okay...but we can do better' color? Well, there is one slight tweak to make it truly re-usable. The get_actual_number is easily overloadable because it takes the number by reference and the compiler can easily distinguish a double& from a short& from a long&. But the read_non_neg returns its double and so we can't overload it for the integer types! All we need to change is moving the number from a return value to a reference argument. Paste on a couple of prototypes and we're ready to package it up into a nice library with a couple of other versions aimed at integral types!
void read_actual_number(double & num, const string & errmsg = ""); void read_non_neg(double & num, const string & prompt = "", const string & domerrmsg = "", const string & daterrmsg = ""); // ... void read_actual_number(double & num, const string & errmsg) { cin >> num; // try to read a number while (cin.fail()) // eek! bad man entered ';'! { cin.clear(); // forget the failure...aaahhhh... cin.ignore(); // throw out THE char that offended >>... if (cin.peek() == '\n') // that was the end of the crap they entered { cout << errmsg; // error message once per line -- if needed } cin >> num; // re-try } return; } void read_non_neg(double & num, const string & prompt, const string & domerrmsg, const string & daterrmsg) { cout << prompt; read_actual_number(num, daterrmsg); while (num < 0) { cout << domerrmsg << prompt; read_actual_number(num, daterrmsg); } return; }
(Well, I guess we should include some comments, too. *grin*)
.
.
.
*aigh* I can't stand it! I have to do it! One more version!!!
void read_actual_number(double & num, const string & errmsg = ""); void read_non_neg(double & num, const string & prompt = "", const string & domerrmsg = "", bool valdat = true, const string & daterrmsg = ""); // ... void read_actual_number(double & num, const string & errmsg) { cin >> num; // try to read a number while (cin.fail()) // eek! bad man entered ';'! { cin.clear(); // forget the failure...aaahhhh... cin.ignore(); // throw out THE char that offended >>... if (cin.peek() == '\n') // that was the end of the crap they entered { cout << errmsg; // error message once per line -- if needed } cin >> num; // re-try } return; } void read_non_neg(double & num, const string & prompt, const string & domerrmsg, bool valdat, const string & daterrmsg) { cout << prompt; if (valdat) { read_actual_number(num, daterrmsg); } else { cin >> num; } while (num < 0) { cout << domerrmsg << prompt; if (valdat) { read_actual_number(num, daterrmsg); } else { cin >> num; } } return; }
Aaaahhhhh! Much better! Now the domain validation desirer...desiree...um... the programmer who wants to validate their data's domain properties without regard for the 'data-li-ness' of it can simply pass that valdat argument as false! Yea!
...
We can also make the system more tightly 'coupled' (instead of two separate functions we merge both tasks into a single function -- hence the two actions are closer or have been coupled together).
cout << prompt; cin >> x; while (cin.fail() || !valid(x)) // !valid(x) being something like x < 0 { if (cin.fail()) { cin.clear(); cin.ignore(); if (cin.peek() == '\n') { cout << daterrmsg << prompt; } } else { cout << domerrmsg << prompt; } cin >> x; }
This version is all tied together, but it really has no particular advantage over the uncoupled versions. And, from a software design point of view, less coupled is better because it is more re-usable.
...
Sequential validation...clunky and less versatile for user...
// note: x must be positive and y must be both positive and // less than x // (i.e. the math book said: 0 < y < x) read x while (x <= 0) { error message read x } read y while (y <= 0 || y >= x) { error message read y }
But...nested validation removes the versatility problem...
// note: x must be positive and y must be both positive and // less than x // (i.e. the math book said: 0 < y < x) do { read x while (x <= 0) { error message read x } read y while (y <= 0) { error message read y } if (y >= x) { error message } } while (y >= x);
And...we can now extract a re-usable portion (or just re-use such a function we already wrote)...
____ read_positive() // re-usable part { read x while (x <= 0) { error message read x } return x } // note: x must be positive and y must be both positive and // less than x // (i.e. the math book said: 0 < y < x) x = read_positive(); y = read_positive(); while (y >= x) { error message x = read_positive(); y = read_positive(); }
...
do { print menu read choice switch (choice) { case '1': case 'A': case 'a': // first menu choice break; case '2': case 'B': case 'b': // second menu choice break; case '3': case 'C': case 'c': // third menu choice break; case '4': case 'Q': case 'q': // quitting... break; default: // invalid choice... break; } } while (choice != '4' && choice != 'Q' && choice != 'q');
Notice how DeMorgan helps us form that complicated loop condition! (We humans think: "When do we stop?" To which the answer is, of course, "When choice is equal to '4' or 'Q' or 'q'." But the computer (C++) wants to know: "When do I get to keep going?" And the answer to this question is the exact logical opposite of the answer to our question! So we end up with:
!(choice == '4' || choice == 'Q' || choice == 'q') | | \ / ' choice != '4' && choice != 'Q' && choice != 'q'
which, though it seems completely wrong on first glance, turns out to be correct! Thanks dead French guy!)
NEVER use a primed validation loop with a menu -- let the switch validate it for you!
...
bool quit = false; do { print menu read choice switch (choice) { case '1': case 'A': case 'a': // first menu choice break; case '2': case 'B': case 'b': // second menu choice break; case '3': case 'C': case 'c': // third menu choice break; case '4': case 'Q': case 'q': quit = true; break; default: // invalid choice... break; } } while (!quit);
...
...