Chapter 5
Input: Letting the Reader Talk Back
Reading what someone types, and the newline that ruins everything.
Every program you’ve written says the same thing every time you run it. Chapter 3 gave you variables, but you were the one filling them in. Let’s hand that job to whoever is running the program.
Asking for a word
#include <iostream>
#include <string>
int main() {
std::cout << "What is your name? ";
std::string name;
std::cin >> name;
std::cout << "Hello, " << name << "!\n";
}
What is your name? Ada
Hello, Ada!
std::cin is the other end of std::cout: the way in. And look at the arrows:
they’ve turned around. With cout the text flows out of your program; with cin
it flows out of the input and into your variable. The arrows always point the way
the data is going, which is the one thing about this syntax that was designed to
help you.
Two details in that program worth copying. The prompt has a space at the end and
no \n, so the cursor waits on the same line, right where the reader is about to
type. And the variable is created before it’s read into, because cin needs somewhere
to put the answer.
The first surprise
Run that program and type your full name:
What is your name? Ada Lovelace
Hello, Ada!
Half your name vanished. >> reads one word. It stops at the first space and
leaves everything after it sitting in the queue, waiting for the next read that
never comes.
That’s not a bug, it’s the job description. >> is for reading one item at a time,
and it decides where an item ends by looking for whitespace. Handy for numbers.
Useless for names.
Reading a whole line
When you want everything up to the end of the line, spaces included, use
getline:
std::string name;
std::getline(std::cin, name);
What is your name? Ada Lovelace
Hello, Ada Lovelace!
Notice the different shape. getline isn’t attached to cin with a dot. It’s a
standalone function that you hand two things: where to read from, and where to put
the result. Chapter 9 explains why some things are written thing.action() and
others action(thing); for now, copy the shape.
Numbers work the same way
std::cout << "How old are you? ";
int age = 0;
std::cin >> age;
>> reads the digits, stops at the space or newline after them, and turns them
into a number for you. This is exactly where >> is good.
The trap
Now put those two together, in the order you’d naturally write them:
std::cout << "How old are you? ";
int age = 0;
std::cin >> age;
std::cout << "What is your full name? ";
std::string name;
std::getline(std::cin, name);
std::cout << "age=[" << age << "] name=[" << name << "]\n";
Type 36, press return, and the program doesn’t even wait for the name:
How old are you? 36
What is your full name? age=[36] name=[]
The name is empty and you never got to type it.
Here’s why. When you typed 36 and pressed return, two things went into the
queue: the characters 36, and the newline from the return key. >> took the
digits and stopped. It has no interest in the newline, so it left it there.
Then getline ran. Its job is to read up to the end of a line, and the very first
thing it found was the end of a line. So it did exactly what you asked: it read
nothing, decided the line was over, and moved on.
Exercise 1 · Cause it, then cure it
Write the age-then-name program without std::cin.ignore() and watch the name
come back empty. Then add the ignore() and watch it work.
Doing it in that order matters. When this happens to you in three weeks’ time in a program you care about, you want to recognise the symptom instantly rather than work it out from scratch.
When the reader types nonsense
Programs meet people. People type words into number boxes:
std::cout << "How many? ";
int count = 0;
std::cin >> count;
std::cout << "count=[" << count << "]\n";
std::cout << "Next word? ";
std::string word = "unchanged";
std::cin >> word;
std::cout << "word=[" << word << "]\n";
Type abc and you get this:
How many? abc
count=[0]
Next word? word=[unchanged]
Two things went wrong, and the second is worse. count came back as 0, a
perfectly plausible number that you did not get told was made up. And then the
next read didn’t happen at all: word still holds what you gave it. cin gave
up when it hit letters where digits should be, and it stays given-up for every
read that follows.
Check yourself
Project
Mad-libs
Roughly 40 minutes
Ask the reader for a few words without telling them what they’re for, then print the result. The comedy is in the gap between the two.
Collect at least:
- a name, using
getlineso two-word names survive - a number, using
>> - a plural noun, using
getline, which is the one that will bite you
Then print a short story with all of them in it:
A name: Ada Lovelace
How many? 12
A plural noun: rubber ducks
Ada Lovelace woke up and found 12 rubber ducks in the kitchen.The point of the project is the ordering problem. You have a >> for the
number sitting between two getlines. Write it the obvious way first, run it,
and watch the third prompt get skipped. Then put std::cin.ignore() in the right
place.
If you write it correctly first time, delete the ignore() and run it anyway.
Seeing the failure is worth more than avoiding it.
Stretch: ask for two more words and make the story longer. Notice that every
question is the same four lines with a different prompt and a different variable
(printing, declaring, reading, and remembering to ignore()). That repetition is
the itch chapter 9 scratches.