Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

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::getline reads 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 and ignore() the bad input.
  • if (std::cin >> value) is the idiomatic success check; handle eof() to avoid infinite loops.
  • <iomanip> manipulators are sticky once set.

Questions about this lesson

Log in to ask a question.