Lesson 7 of 9
You've been printing since Module 2. Reading is where the surprises live.
#include <iostream>
#include <string>
int main() {
int age = 0;
std::string name;
std::cout << "Age: ";
std::cin >> age;
std::cout << "Name: ";
std::cin >> name;
std::cout << name << " is " << age << "\n";
return 0;
}
std::cin >> x reads whitespace-separated tokens, converting to the variable's type. Type
25 Ada and you get both.
>> stops at whitespace
#include <iostream>
#include <string>
int main() {
std::string first;
std::cin >> first; // "Ada" from "Ada Lovelace"
std::string whole;
std::getline(std::cin, whole); // the rest of the line
return 0;
}
>> on a std::string stops at the first space. For a full line — a name, an address, a task
description — use std::getline.
The trap: mixing >> and getline
This bites everybody:
int age;
std::string name;
std::cin >> age; // reads 25, leaves the '\n' in the buffer
std::getline(std::cin, name); // reads that leftover '\n' — empty string!
>> consumes the number but leaves the newline you pressed. getline then reads up to the
next newline, finds one immediately, and hands you an empty string without waiting.
The fix is to discard the rest of the line first:
#include <iostream>
#include <limits>
#include <string>
int main() {
int age = 0;
std::string name;
std::cin >> age;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::getline(std::cin, name);
return 0;
}
ignore skips characters until it hits '\n' (or that many characters, which is effectively
"however many"). Any time you follow >> with getline, you need it.
Failure: the part nobody mentions
If input doesn't match the type, extraction fails — and what happens to your variable depends on how it failed. All three of these leave the stream in a failed state:
#include <iostream>
#include <sstream>
int main() {
std::cout << std::boolalpha;
{ // no input at all
std::istringstream in("");
int n = -1;
in >> n;
std::cout << "empty: n=" << n << " fail=" << in.fail() << "\n";
}
{ // not a number
std::istringstream in("abc");
int n = -1;
in >> n;
std::cout << "text: n=" << n << " fail=" << in.fail() << "\n";
}
{ // too big for an int
std::istringstream in("99999999999999999999");
int n = -1;
in >> n;
std::cout << "overflow: n=" << n << " fail=" << in.fail() << "\n";
}
return 0;
}
empty: n=-1 fail=true
text: n=0 fail=true
overflow: n=2147483647 fail=true
Three different outcomes for the variable — left alone, zeroed, or clamped to INT_MAX — and
one thing in common: fail() is true.
That's the lesson. Never trust the variable; check the stream. Code that reads a number and carries on without checking will happily proceed with 0, or with 2147483647, and you'll never know where the nonsense came from.
(std::istringstream reads from a string instead of the keyboard. It behaves exactly like
std::cin, which makes it the right tool for testing input code — and for these examples.)
Recovering
Once a stream fails, every later read fails too until you clear it. Two steps:
#include <iostream>
#include <limits>
#include <sstream>
int main() {
std::istringstream in("abc 42");
int n = 0;
in >> n;
std::cout << "first attempt failed: " << std::boolalpha << in.fail() << "\n";
in.clear(); // reset the error flags
in.ignore(std::numeric_limits<std::streamsize>::max(), ' '); // drop the bad token
in >> n;
std::cout << "second attempt: n=" << n << " fail=" << in.fail() << "\n";
return 0;
}
first attempt failed: true
second attempt: n=42 fail=false
clear() resets the flags. ignore discards the offending characters — without it the bad
token is still sitting there and the next read fails identically, which is how you get an
infinite loop asking "enter a number" forever.
The robust input pattern
Put it together and you get the loop you should actually use:
#include <iostream>
#include <limits>
int read_int(const std::string& prompt) {
int value = 0;
while (true) {
std::cout << prompt;
if (std::cin >> value) {
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return value;
}
if (std::cin.eof()) {
return 0; // input ended — give up
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Not a number, try again.\n";
}
}
That's a fragment (it needs <string> and a main to run), but it's the shape to copy. Note
if (std::cin >> value) — a stream converts to bool as "did that succeed?", which is the
idiomatic check. And the eof() case matters: without it, piping a file that ends unexpectedly
spins forever.
Output formatting
#include <iomanip>
#include <iostream>
int main() {
double pi = 3.14159265;
std::cout << pi << "\n";
std::cout << std::fixed << std::setprecision(2) << pi << "\n";
std::cout << std::setw(8) << 42 << "|\n";
std::cout << std::left << std::setw(8) << 42 << "|\n";
return 0;
}
3.14159
3.14
42|
42 |
<iomanip> gives setprecision, setw (field width), and alignment. These are sticky —
they stay in effect until changed, which is a frequent source of confusion when output three
functions later is mysteriously formatted.
That stickiness, plus the verbosity, is why C++20 added a better option — the next lesson.
Try it
Using std::istringstream so it's testable, write a function that reads two integers from a
string and reports whether both were read successfully. Test it with "3 4", "3 abc", and
"3".
Checkpoint
>>reads whitespace-separated tokens;std::getlinereads a whole line.- Mixing them requires
cin.ignore(...)to discard the leftover newline. - On failure the variable may be unchanged, zeroed, or clamped — check
fail(), not the value. - A failed stream stays failed:
clear()the flags andignore()the bad input. if (std::cin >> value)is the idiomatic success check; handleeof()to avoid infinite loops.<iomanip>manipulators are sticky once set.
Questions about this lesson
Log in to ask a question.