Перейти к содержимому

I/O: потоки, форматирование и std::print

Мастер C++ I/O от классического iostream до современного std::format и std::print, включая состояния потока, файл I/O и общие ошибки буфера.

Эволюция C++ I/ О!

C++ I/O значительно изменился в истории языка. Классическая библиотека iostream (std::cin, std::cout) является стандартом со времен C++98, но она имеет хорошо известные проблемы эргономики и производительности. C++20 представила std::format, библиотеку безопасного форматирования, вдохновленную str.format() Python. C++23 добавил std::print, который объединяет форматирование и вывод в одном вызове.

Понимание всех трех подходов важно: iostream повсюду в существующем коде, std::format - это современный способ форматирования строк, а std::print - это будущее выхода C++.

Классический I/O: cout, cin, cerr

Библиотека iostream предоставляет потоковые объекты для стандартного ввода, вывода и вывода ошибки:

io_basics.cppc++23
#include <iostream>
#include <string>
#include <limits>    // For numeric_limits

int main() {
    // Output with cout
    std::cout << "Hello, " << "World!" << '\n';      // Chained insertion
    std::cout << "The answer is: " << 42 << '\n';    // Automatic type formatting

    // Error output — cerr is unbuffered (immediate), clog is buffered
    std::cerr << "Error: something went wrong\n";

    // Input with cin
    std::cout << "Enter your name: ";
    std::string name;
    std::getline(std::cin, name);         // Read entire line (including spaces)

    std::cout << "Enter your age: ";
    int age{};
    std::cin >> age;                       // Read a single value (stops at whitespace)

    // Check if input succeeded
    if (std::cin.fail()) {
        std::cerr << "Invalid input!\n";
        std::cin.clear();                  // Reset error flags
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // Discard bad input
    }

    // The getline-after-cin trap:
    // After cin >> age, a '\n' remains in the buffer.
    // A subsequent getline() would read an empty string!
    // Fix: call cin.ignore() before getline():
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    std::string city;
    std::cout << "Enter your city: ";
    std::getline(std::cin, city);          // Now works correctly

    std::cout << name << " (" << age << ") from " << city << '\n';
}
Вывод

Нажмите «Запустить», чтобы скомпилировать и выполнить этот пример.

Государственные флаги Stream

Каждый поток поддерживает набор государственных флагов, которые указывают его текущее состояние:

goodbit - Без ошибок. Поток готов к операциям ввода/вывода.
- eofbit Конец файла / ввода был достигнут. Установите, когда поток пытается читать дальше конца.
failbit** — произошла логическая ошибка (например, попытка прочитать целое число, но встречающийся текст). Поток по-прежнему можно использовать после вызова clear().
- badbit Произошла непоправимая ошибка ввода/вывода (например, сбой диска). Поток, как правило, непригоден.

Вы проверяете их с помощью stream.good(), stream.eof(), stream.fail(), stream.bad() или с помощью потока в булевом контексте: if (std::cin) эквивалентен if (!std::cin.fail()).

Критический шаблон: всегда проверяйте состояние потока после операций ввода , а не раньше. Обычный идиом while (std::cin >> value) считывает и проверяет в один шаг — он останавливается, когда считывание не удается.

Подводный камень

Наиболее распространенная ошибка ввода/вывода в C++ включает смешивание >> (форматированная экстракция) с std::getline(). Оператор >> считывает значение, но оставляет след в буфере. Последующий std::getline() затем читает оставшуюся новую строку как пустую строку, пропуская вход.

Решение состоит в том, чтобы позвонить std::cin.ignore(std::numeric_limits::max(), '\n') между >> и std::getline(), чтобы отказаться от оставшейся новой линии.

Другой подход заключается в использовании std::getline() для всех входных данных и самостоятельном разборе строк с std::istringstream или std::stoi/std::stod. Это полностью устраняет проблему и дает вам лучшую обработку ошибок.

  • После cin >> x в буфере остается новая строка — следующая getline() читает ее как пустую строку.
  • Исправление: call cin.ignore(numeric limits::max(), '\n') между >> и getline()
  • Альтернатива: используйте getline() для ВСЕХ входных и разборных строк с stai/stod или istringstream
  • Всегда проверяйте состояние потока после ввода: если (cin >> x), а не читать вслепую

Файл I/O

Файл I/O использует тот же интерфейс потока, что и консоль I/O, с std::ifstream для чтения и std::ofstream для записи:

file_io.cppc++23
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main() {
    // Writing to a file
    {
        std::ofstream out("scores.txt");  // Opens for writing (creates or truncates)
        if (!out) {
            std::cerr << "Failed to open file for writing\n";
            return 1;
        }
        out << "Alice 95\n";
        out << "Bob 87\n";
        out << "Charlie 92\n";
    }  // File is automatically closed when 'out' goes out of scope (RAII!)

    // Reading from a file
    std::ifstream in("scores.txt");
    if (!in) {
        std::cerr << "Failed to open file for reading\n";
        return 1;
    }

    std::string name;
    int score{};
    std::vector<std::pair<std::string, int>> results;

    while (in >> name >> score) {           // Read until failure (EOF or bad data)
        results.emplace_back(name, score);
    }

    for (const auto& [n, s] : results) {
        std::cout << n << ": " << s << '\n';
    }

    // Appending to a file
    std::ofstream append("scores.txt", std::ios::app);  // Open in append mode
    append << "Diana 98\n";

    // Reading entire file line by line
    std::ifstream in2("scores.txt");
    std::string line;
    while (std::getline(in2, line)) {
        std::cout << "Line: " << line << '\n';
    }
}
Вывод

Нажмите «Запустить», чтобы скомпилировать и выполнить этот пример.

Современное форматирование: std::format & std::print

C++20 std::format и C++23 std::print обеспечивают типобезопасное, похожее на Python форматирование, которое является более безопасным и читаемым, чем манипуляторы iostream:

format_print.cppc++23
#include <format>   // C++20
#include <print>    // C++23
#include <string>
#include <vector>
#include <numbers>  // C++20: mathematical constants

int main() {
    // std::format (C++20) — returns a formatted std::string
    std::string msg = std::format("Hello, {}!", "World");
    // msg == "Hello, World!"

    // Positional arguments
    std::string pos = std::format("{1} comes before {0}", "second", "first");
    // pos == "first comes before second"

    // Formatting specifiers
    std::string nums = std::format(
        "int: {:>10d}\n"    // Right-aligned, width 10, decimal
        "hex: {:#06x}\n"    // '0x' prefix, width 6, zero-padded hex
        "float: {:.3f}\n"   // 3 decimal places, fixed notation
        "sci: {:.2e}\n"     // 2 decimal places, scientific notation
        "pi: {:.10f}",      // 10 decimal places
        42, 255, 3.14159, 12345.6789, std::numbers::pi
    );

    // std::print (C++23) — format + output in one step
    std::print("Name: {}, Age: {}\n", "Alice", 30);
    std::print("Pi is approximately {:.6f}\n", std::numbers::pi);

    // std::println (C++23) — like print but adds a newline
    std::println("Score: {:>5}", 95);       // "Score:    95"
    std::println("Score: {:>5}", 100);      // "Score:   100"

    // Formatting containers — print each element
    std::vector<int> values = {10, 20, 30, 40, 50};
    for (std::size_t i = 0; i < values.size(); ++i) {
        std::println("[{:2}] = {:>4}", i, values[i]);
    }

    // Why prefer std::format/print over iostream?
    // 1. Type-safe (unlike printf)
    // 2. Readable format strings (unlike iostream manipulators)
    // 3. No state leakage (iostream manipulators are sticky!)
    // 4. Better performance in many cases
}
Вывод

Нажмите «Запустить», чтобы скомпилировать и выполнить этот пример.

Лучшая практика

Современный C++ предлагает три подхода ввода/вывода. Вот когда использовать каждый:

Используйте std::print/std::println (C++23) для нового кода, когда ваш компилятор поддерживает его. Это самый чистый синтаксис, безопасный по типу и позволяет избежать проблем с государственными манипуляторами.

Используйте std::format (C++20), когда вам нужна отформатированная строка (не прямой выход), или когда ваш компилятор поддерживает C++20, но не C++23.

Используйте iostream при взаимодействии с кодом, который ожидает потоковых объектов, когда вам нужна абстракция потока (например, чтение с cin, файл I/O) или при работе с устаревшими кодовыми базами.

Избегайте printf/scanf от C — они не являются безопасными по типу (прохождение неправильного спецификатора формата является неопределенным поведением) и не работают с типами C++, такими как std::string.

  • Предпочтительнее std::print/println (C++23) для нового выходного кода — чистейший и безопасный
  • Используйте std::format (C++20), когда вам нужна отформатированная строка, а не прямой выход
  • iostream по-прежнему необходим для cin, file I/O и потоковых абстракций.
  • Избегайте printf/scanf — они не безопасны для типов и не могут обрабатывать std::string напрямую
Ключевые выводы
  • Всегда проверяйте состояние потока после операций ввода — используйте «if (cin >> x)» или «while (getline (in, line)»).
  • Смешивание cin >> и getline() вызывает ошибку newline-in-buffer — позвоните cin.ignore() между ними
  • Файловые потоки используют RAII — они закрываются автоматически, когда выходят из поля зрения.
  • std::format (C++20) обеспечивает типобезопасное, Python-подобное форматирование строк
  • std::print/println (C++23) сочетает форматирование и вывод — предпочтительный способ для нового кода
  • Манипуляторы iostream являются государственными (липкими) — std::format полностью избегает этой проблемы

Тест — проверьте свои знания

(10 XP)

1. Что произойдет, если вы вызовете `std::getline(std::cin, str)` сразу после `std::cin >> number`?

2. Что производит `std::format("{:#06x}", 255)`?

3. Почему потоки файлов (если поток / поток) автоматически закрываются, когда они выходят из области действия?