Chapter 04 of 24 | C++

Input and output

Streams provide a consistent interface for terminal and file I/O. Include <iostream> for std::cin, std::cout, and std::cerr. The extraction operator >> reads a formatted value; insertion with << writes one.

#include <iostream>
#include <string>

int main()
{
    std::string name;
    int age{};

    std::cout << "Name and age: ";
    if (std::cin >> name >> age) {
        std::cout << "Hello, " << name
                  << ". Next year you will be " << age + 1 << ".\n";
    } else {
        std::cerr << "Please enter a name and a whole-number age.\n";
        return 1;
    }
}

Formatted extraction skips leading whitespace and reads one token at a time. That is convenient for numbers and single-word fields, but a name containing spaces is split. Use std::getline(std::cin, text) to read an entire line.

Mixing tokens and lines

After std::cin >> number, the line-ending character is still waiting in the input buffer. A following getline may consume that empty remainder. If you intend to move to the next line, use std::getline(std::cin >> std::ws, text) to discard leading whitespace before reading the line. Do not use this form when leading spaces in the line are meaningful.

Formatting output

Separate values with explicit spaces or newlines; streams do not add them automatically. For decimal formatting, include <iomanip> and use manipulators such as std::fixed and std::setprecision(2). Formatting manipulators can affect later output on the same stream.

Input is not guaranteed to be valid

When extraction fails, the stream enters a failed state and subsequent reads do not proceed normally. For robust interactive programs, report the error clearly and either stop or deliberately clear the state and discard the bad line before retrying. Never assume a user or file supplied a valid number.

Understand stream state

A stream records whether an operation succeeded. When an integer extraction is given text that cannot begin an integer, extraction fails and the destination is not a valid new answer to use. The stream then remains in a failed state until the program handles that condition. Checking the result directly in an if statement is both concise and correct because streams convert to a state that can be tested.

int quantity{};
if (!(std::cin >> quantity)) {
    std::cerr << "Quantity must be a whole number.\n";
    return 1;
}

For a retry loop, the recovery steps matter: clear the failure flags, discard the invalid characters through the end of the line, then prompt again. Clearing the state without discarding the bad characters can cause the same input to fail repeatedly. A program that reads a single value can often report the problem and exit rather than building a complicated recovery flow.

Tokens versus lines in a concrete example

Suppose a user enters 17 and presses Enter. The extraction operation reads the digits as an integer, while the newline remains in the input buffer. A subsequent plain getline sees that newline immediately and returns an empty string. Using std::getline(std::cin >> std::ws, line) consumes leading whitespace before reading the next line. Be cautious: std::ws consumes all leading whitespace, including intentional indentation at the start of the line.

An alternative is to read complete lines consistently and parse each line separately. That approach can make validation easier because the program can reject trailing characters or give the user a precise explanation. Choose one approach that matches the input format instead of mixing them accidentally.

Output is a stream, too

Use std::cout for normal output and std::cerr for diagnostic messages. They are separate streams so a shell can redirect them independently. A prompt often does not end with a newline so the user can type beside it; when output is buffered, interactive prompts may need an explicit flush. The stream insertion operator can chain multiple values, which is convenient, but insert separators yourself because concatenated output otherwise runs together.

Representing numbers for display

Formatting changes presentation, not the underlying numeric value. std::fixed asks for fixed-point notation and std::setprecision(2) then sets the number of digits after the decimal point. Without std::fixed, precision generally controls significant digits instead. These manipulators remain active for later output on the same stream, so set the desired format near the output that depends on it.

Practice

  1. Read a first and last name on one line, then print them back.
  2. Read a measurement and show it to two decimal places.
  3. Enter text where a number is expected. Confirm that your program reports failure rather than using an uninitialized value.
  4. Read an integer followed by a full sentence and handle the newline between them correctly.
  5. Write invalid input to std::cerr and normal results to std::cout.

Key idea: streams carry both data and status. Check extraction before relying on a value, and choose token-oriented or line-oriented input intentionally.

Formatted extraction is a parsing operation

The formatted-input machinery constructs a sentry object, handles stream state and whitespace rules, and then asks the locale's facets to interpret characters as a value. This is convenient for human-oriented input, but it is more than copying bytes into a variable. Locale can affect formatting conventions, and extraction may accept a valid prefix while leaving trailing characters for the next operation. If the entire input must match a grammar, read the whole record and validate that every character was consumed.

Separate the transport layer from the parsing layer. First read a line reliably; then parse that line according to the program's grammar and return a clear success or error result. This makes it possible to give precise diagnostics, avoid accidental token leftovers, and test parsing without an interactive terminal.

Formatting, locale, and precision

Streams carry formatting state, including precision, alignment, base, and locale. A manipulator may therefore affect output that follows it. When a function changes stream formatting, either restore the previous state or document that it modifies the stream. For machine-readable interchange, prefer a locale-independent representation; a decimal comma or localized grouping separator can make a file ambiguous to another program.

Floating-point output precision controls the textual representation, not the underlying arithmetic accuracy. If text must round-trip to the same floating-point value, use an appropriate precision and a parser with compatible rules. For human display, choose precision based on the domain and label units clearly.

Fast and low-level parsing

For high-volume numeric text, stream formatting may be slower than specialized conversion functions. C++17 provides std::from_chars for locale-independent, non-allocating parsing of many integer and floating-point representations; support for floating-point overloads arrived later and can vary by library version. It reports both an error code and the first unconsumed character, allowing callers to check range and full consumption explicitly.

Performance-sensitive parsing should be justified by measurement. A faster parser is still incorrect if it accepts malformed data or ignores overflow. Define the accepted grammar, test boundary cases, and compare the optimized parser against a trusted reference implementation.

Advanced exercise

  1. Read a complete line, parse one integer, and reject trailing non-whitespace characters.
  2. Compare formatted extraction and std::from_chars for the same input set, including malformed and out-of-range values.
  3. Write a parser test table covering empty input, leading whitespace, signs, overflow, trailing text, and valid boundary values.
0% read