In this chapter, we’ll look at repeated actions, input, and things to do with the character type.
Keyboard input
This prints the query about age, then waits for keyboard input. If the user enters a number, that number is stored in ageInYears. (Anything else is likely to give ageInYears a 0 value.) ssin 1 waits for you to press Enter before it processes input, so backspacing is allowed.
ssin uses the same font and cursor as sout; they are both part of SSDL.
You may note how the << arrows go: with sout, they go from the value to the output; with ssin, they go from the input to the variable.

Insulting the world, one person at a time
Whether reading chars or numbers or whatever, ssin skips whitespace (spaces, tabs, and returns); so you can type what you want with spaces between, and it can handle it.
A program using ssin
Antibugging
- You get a string of error messages like this:2main.cpp: In function 'int main(int, char**)':main.cpp:11:39: error: no match for 'operator<<' (operand types are 'std::istream' {aka 'std::basic_istream<char>'} and 'int')sout << "How old are you? "; ssin << ageInYears;~~~~~^~~~~~~~~~~~~main.cpp:11:39: note: candidate: 'operator<<(int, int)' <built-in>main.cpp:11:39: note: no known conversion for argument 1 from 'std::istream' {aka 'std::basic_istream<char>'} to 'int'In file included from /usr/include/c++/8/string:52,from /usr/include/c++/8/bits/locale_classes.h:40,from /usr/include/c++/8/bits/ios_base.h:41,from /usr/include/c++/8/ios:42,from /usr/include/c++/8/istream:38,from /usr/include/c++/8/sstream:38,from ../../external/SSDL/include/SSDL_display.h:26,from ../../external/SSDL/include/SSDL.h:27,from main.cpp:4:/usr/include/c++/8/bits/basic_string.h:6323:5: note: candidate: 'template<class _CharT, class _Traits, class _Alloc> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&)'operator<<(basic_ostream<_CharT, _Traits>& __os,^~~~~~~~
and literally pages more. Good luck decoding that.
It all came from one error: the >>’s went the wrong way on an ssin statement. It should have been ssin << ageInYears. Compilers sometimes get confused.
You may get another flood of errors if you try to ssin >> "\n" or something else that isn’t a variable.
- 1.
Write a program for converting inches to centimeters, using the formula centimeters = 2.54 * inches. Make it interactive, that is, ask the user for the value to convert.
- 2.
Write a program that identifies what generation you’re in (Gen Z, millennial, etc.), based on the age or year of birth the user inputs. You get to pick the ranges.
- 3.
The Body Mass Index (BMI) tells you if you’re heavy, thin, or in the middle. (It’s imprecise, but if nothing else, maybe I could convince my grandmother I won’t starve if I don’t take seconds.)
Per Wikipedia, these are the ranges:
Underweight | Less than 18.5 |
---|---|
Normal weight | 18.5–25 |
Overweight | 25–30 |
Obese | 30+ |
So, write a program to calculate the user’s BMI. The formula isBMI = Weight in kg/(Height in meters)2
- 4.
Write a program that asks the user for two times (such as 1:06 or 12:19) and prints the difference neatly (e.g., 11:03 or 0:40, but not 13:0 or -12:70). You provide the times with keyboard input – we’re not asking the computer what time it is.
- 5.
…but now we are. Instead of asking the user for the times, measure the two times that the user presses the return key, getting the current system time like so:
int myTime = time (nullptr);
This gives you time in seconds since midnight, January 1, 1970 (on systems I know). You’ll need to #include <ctime>.
while and do-while
The program can do something if a condition is true…or it can do something while a condition is true.
As long as the condition is true, the while loop will execute the action. When it stops being true, the loop ends, and the program moves on to whatever comes after.
That wouldn’t work as a while statement, while (answer != 'n' && answer != 'y') ..., because you don’t know what answer is until you’ve asked at least once.
The bottom line is that do-while does the action at least once (before testing the condition), whereas while might quit before taking any action at all. We usually use while, but sometimes do-while is the very thing we need. Thus, we have the Golden Rule of Loops.
If you want the loop to execute at least once, use do-while.
If it makes any sense for it to execute zero time, use while.
Loops with SSDL
SSDL also doesn’t check for things that make it quit the program – pressing Escape or clicking the X to kill the window – until it’s waiting on you. So the preceding code won’t let you quit, either.
In that last do-while loop, we could ask the user to type 1 for yes and 2 for no, if we wanted to expose ourselves as user-hostile throwbacks to the 1970s and never get hired again. (What does 2 have to do with “no”?) It’s much easier on the user to remember that 'n' means no.
If there are more options to choose than yes and no – say, your program manipulates files, opening, saving, and renaming – it’s still user-friendly to give options with letters (O, S, and R) rather than numbers.
How to make your programs easy to interact with is the subject of one subfield of computer science: human-computer interaction.
break and continue
continue means skip the rest of this iteration of the loop and go back to the top. I rarely use it.
Some programming-style mavens are horrified by break and continue. They think you should be able to look at the loop’s continuation condition and see immediately under what circumstance the loop can end – essentially, that these keywords reduce clarity. I agree clarity is crucial, but I’m not sure break is the problem. Certainly if a loop is 50 lines long, it’ll be tedious to examine it for breaks. But I think the solution is not to have loops 50 lines long. Simple is good.
Antibugging
- The program won’t end, and you can’t even kill the program. You’re probably stuck in a loop, but how do you stop it? First, try Ctrl-C (hold Ctrl down and press C). If that doesn’t work, try these actions:
Visual Studio: Debug ➤ Stop Debugging or click the reddish square for stop near the top of the window.
MinGW: Kill it with Task Manager.
Unix: If you don’t even have a command prompt, press Ctrl-Z in the command window to get it.
kill -9 <process-id> means something like “I tried, but I can’t find a nice way to end this process, so just kill it.”
a.out is what we’re trying to kill, but if we ran it with a script like runx, we’d want that gone too. It’s probably the most recent shell command, some command with “sh” in its name. (Get the wrong one and you may kill your terminal. Oops.) This command will kill it and its dependent process a.out:
- The loop repeats forever, and you can’t quit. Maybe it isn’t checking for quit messages. Make your loop condition look like thiswhile (! SSDL_IsQuitMessage () &&...whatever else you want to check... )...;or, if it’s a do-while,do{...}while (! SSDL_IsQuitMessage () && ...);
The loop repeats forever until you click quit or does something forever that you wanted done a few times.
Consider under what condition you break the loop. It must be that it’s never met:int rectanglesDrawn = 0;while (!SSDL_IsQuitMessage () &&rectanglesDrawn < MAX_RECTANGLES){SSDL_RenderDrawRect (...);}The loop never incremented rectanglesDrawn…so no matter how many you draw, the loop doesn’t end. This line should do it:...rectanglesDrawn += 1;}- The loop repeats forever, or won’t repeat when it should. It’s easy to get confused when the loop has a combination of conditions:do{sout << "Answer Y or N: "; ssin >> answer;}while (! SSDL_IsQuitMessage () && (answer != 'n' || answer != 'y'));
This may look right, but it actually says keep looping while nobody said quit, and the answer is not yes or not no. Well, it’s always either not yes or not no! Suppose it’s yes. Then “not no” is true, so it keeps going. Suppose it’s no. Then “not yes” is true, so it keeps going.
The solution is to keep going while it’s not yes and it’s also not no – while it’s a nonsensical answer like '7' or 'X':do{sout << "Answer Y or N: "; ssin >> answer;}while (! SSDL_IsQuitMessage () && (answer != 'n' && answer != 'y'));
- 1.
Have the user keep entering a number until he/she guesses some number you pick.
- 2.
…and have the program print how many guesses it took.
- 3.
Write a program that asks for a (capital) letter and counts from 'A' till it finds it. Its output will be something like 'E' is the 5th letter of the alphabet!
- 4.
…now adapt the program so it keeps repeating till you give it a '.' to end.
- 5.
Write a program that draws a bubble wherever you click. The bubble’s size should depend on how long since the last mouse click. Use the Internet to read up on the function SDL_GetTicks() .
- 6.
Update Exercise 2 from the end of the previous chapter – the hidden object game – so the user can click the hidden objects in any order.
- 7.
Make your own music player: put boxes marked “Music on” and “Music off” near the bottom of the screen, and turn sound on or off appropriately when the user clicks one of the boxes.
- 8.
(Harder; requires geometry) Consider the perimeter of a triangle which is centered on a point and whose endpoints are “R” away from that center.
Now consider if it were a square, or a pentagon, or something with N sides (Figure 5-2). What will the perimeter look like when N is large?Figure 5-2Polygons for Exercise 4
Write a program that (a) draws a regular N-sided polygon, for some large N and with some radius R, and (b) finds the perimeter. Divide that perimeter by 2R. Given the shape you see on the screen, would you expect that ratio to be close to π? Is it what you expected?
for loops
And here’s its output: 0 1 2 3 4 5 6 7 8 9
Let’s look at that piece by piece.
The initialization section – int count=0 – is done when the loop starts. As you can see, you can declare variables in it. The variables are only visible inside the loop.
As long as the continuing condition is true, the loop continues.
At the end of each time through the loop, each “iteration,” C++ does the increment part. This could be anything, but it usually increments an index variable (i.e., the variable we’re using to count with).
- 1.
Do <initialization section>.
- 2.
Is <continuing-condition> true? If not, leave the loop.
- 3.
Do <action>.
- 4.
Do <increment-section>.
- 5.
Go back to step 2.
Increment operators
Most computers have a built-in instruction to add 1 and another to subtract 1 – so we’re telling the compiler to use them. It’s efficient.
There’s another type of increment, called “post-increment,” and a corresponding post-decrement. It looks like this: count++, not ++count. Here’s how they differ:
Pre-increment: Y = ++X means X = X+1; Y = X. That is, add 1 to X; Y gets X’s new value.
Post-increment: Y = X++ means Y = X; X = X+1. That is, add 1 to X; Y gets X’s old value.
You won’t notice the difference unless you put the expression on the right side of an = or as an argument to a function.
An example: Averaging numbers
A program to average numbers, using a for loop
The keywords break and continue, by the way, work in for loops just as they do in while and do-while loops. They’re available, but not, in my experience, useful.
So we now have three kinds of loops. You know how to decide between while and do-while – in the Golden Rule of Loops (Version 1), earlier in this chapter. What about for loops?
By convention and by reason, we use for loops when we’re counting through a range – when we know what we’re counting from and to. Thus, we have the final Golden Rule of Loops.
If you know in advance how many times you’ll do it, use for. Otherwise:
If you want the loop to execute at least once, use do-while.
If it makes any sense for it to execute zero times, use while.
Antibugging
Later actions are done once, not many times – a common problem. Here’s an example:
No action gets repeated.
Your loop goes one step too far.
The last time through, ++i, makes i equal 4. But if you wanted four entries, you just got five: 0, 1, 2, 3, 4. Solution: Use < for the condition.
For loops almost always start at 0 and use <, not <=, for the continuation condition: i < howManyTimes, not i <= howManyTimes.
- 1.
Print the numbers 1–10…and their squares (1, 4, 9, 16, etc.).
- 2.
Using characters now, use a for loop to print the letters A–Z.
- 3.
…backward.
- 4.
Write a program like the average program in Example 5-2, but let it provide not average but maximum.
- 5.
Adapt Example 3-3/Example 3-4 (drawing a star) so that it asks the user for the radius, center, and number of points – and use a loop rather than repeating code to draw the lines.
- 6.
Write a program which asks the user for an integer and a power to raise it to and prints the result. Calculate with for, of course.
- 7.
Write a program which has the user guess a number (that number can be a constexpr) and keeps taking guesses until the user runs out of turns – you decide how many – or gets it right. Then it reports success or failure. You’ll need a Boolean variable isSuccess.
- 8.
(Hard) Draw a graph of some function (sine is a good one). Add X and Y axes and appropriate labels.
chars and cctype
A program to compare characters
It mostly works. It’s a little strange that 'a' comes after 'Z'. But that’s how the computer thinks of it: lowercase letters, a–z, come after the uppercase range. The precise ordering of the characters was decided in 1967 and is maintained by the American National Standards Institute (ANSI). A complete listing of this American Standard Code for Information Interchange (ASCII) codes is in Appendix C.
It looks weird, but…to get the uppercase version of 'b', we do this: subtract 'a' first. This gives us a difference of 1, of course. We then add this 1 to 'A', which gives us 'B'. This will work for any lowercase letter.
Some useful functions regarding capitalization
int islower (int ch); | Return whether ch is lowercase. (Non-letter characters are not lowercase.) |
int isupper (int ch); | Return whether ch is uppercase. (Non-letter characters are not uppercase.) |
int tolower (int ch); | Return the lowercase version of ch. If ch is not a letter, it returns ch. |
int toupper (int ch); | Return the uppercase version of ch. If ch is not a letter, it returns ch. |
These functions existed in the C, the language C++ grew out of. This explains something that looks odd: we’re dealing with characters, but the type is not char but int! Well, characters are like integers, in a way, so this is tolerable, if not absolutely clear.
Example 5-3, using true alphabetical order rather than simple ASCII order
Antibugging
- You try to print a char with converted case, and it prints a number instead:sout << "The upper-case version of '" << char1<< "' is ' << toupper (char1) << " '.\n";
If we run this, the output will be something like
You’re assigning to a char and get something like “Cannot convert from const char[2] to char.”
This code looks right, but is not: char c = "x"; // wrong!
chars need single quotes, like so: char c = 'x';
The way to remember: Single quotes are for single chars. Double quotes are for two (or more) chars, that is, “quoted text.”
So far we’ve seen these types:
int double float bool char
Some can have modifiers. For example, a long double has more decimal places than a regular double; how many is compiler-dependent. int can be preceded by the keywords signed , unsigned, short, long, or long long (I guess the word “humongous” was taken), as in unsigned long int. As you can imagine, short and long refer to how big the number can be. int may be omitted: unsigned short x; or long y;.
If not specified, an int is signed. If a char is not specified as signed or unsigned, it is up to the compiler to decide which it is. It shouldn’t matter.
wchar_t (“wide character”) is a larger character type, used when char isn’t big enough, that is, for international characters. char8_t , char16_t , and char32_t are also for international characters.
Suffixes on the literal values – as in 5.0f or 42u – are there to tell the compiler “this is a (f)loat, not a double,” “this is (u)nsigned,” and so on. Suffixes can be uppercase.
If you want to know just how big an int, long int, and so on can be, you can find out using #include <climits >, which defines constants for maximum and minimum values for the various types. You can get the size of one of these in bytes3 with sizeof : sizeof (int) or sizeof (myInt), where myInt is an int.
If you store values that are too big, they’ll wrap around; with signed, instead of a too-large positive number, you’ll have a negative number. This is almost never a problem. If it is, use a long int or a long long int.
For a complete list of basic types, see Appendix D.
- 1.Write a program to determine whether the creature you just saw was a fairy, troll, elf, or some other magical creature, assuming you carried your computer into the Enchanted Forest. You pick the distinguishing features of each type of creature. A session might start like so:Welcome to the Enchanted Forest.This creature you have seen:Does it have wings (Y/N)? Y...
The user should be able to type either 'y' or 'Y' and either 'n' or 'N' to answer. (If the user types something that doesn’t make sense, you can assume that means “no.”)
switch
It will work, but there’s an easier way.
The * means “as many copies as you want, maybe zero.”
What this does: The expression in the parentheses is evaluated. (It has to be something you can count by – integers or characters, no floats, no doubles.) If it matches a particular value, the computer goes to that case <value> and executes whatever actions come after. If you specify a default action, that’s what happens if the expression doesn’t match anything.
If letter matches 'A', it does whatever action(s) it finds after case 'A', which in this case is sout << "vowel";. It keeps going till it finds break, which, just as before, means “leave this structure” – so at that point it leaves the switch statement. (Nobody will gripe at you for using break in this way; switch needs it.)
Using a switch statement’s default to catch bad input
Antibugging
switch does what you wanted for that value; then it does the options that follow as well. This is the most common error with switch: forgetting the break. Solution: Go back and put breaks between the different options you wanted (in Example 5-5, 'J', 'M', and 'Y').
- The compiler complains something about case labels and variables. This code has that problem:switch (myChar){case 'P':int turns = MAXTURNS;playGame (turns);break;...}It doesn’t like initializing a variable as part of a switch. No problem. We’ll just put {}’s around the area that needs the variable:switch (myChar){case 'P':{int turns = MAXTURNS;playGame (turns);break;}...}
- 1.
Write and test a function that, given a number, prints the associated ordinal: that is, for 1, print 1st; for 2, print 2nd; for 3, print 3rd; and for everything else, print the number plus “th.”
- 2.
Have the user enter two single-digit numbers and return the sum. Here’s the twist: the numbers entered are in base 16 (“hexadecimal”). In base 16, we use 'A' to represent 10, 'B' for 11, 'C' for 12, and so on to 'F' for 15. You can give the result in base 10.
- 3.
Menus are a time-honored (old-fashioned) way of getting user input. Make a menu offering to draw for the user a circle or a line or maybe some other shape and then draw the selected shape.