Sari la conținut

I/O: fluxuri, formatare și std::print

Stăpânește I/O C++ de la iostream clasic la formatul standard standard și std::print modern, inclusiv stările fluxului, I/O fișiere și capcanele comune ale bufferului.

Evoluția C++ I/O

C++ I/O a evoluat semnificativ de-a lungul istoriei limbii. Biblioteca clasică iostream (std::cin,std::cout) a fost standard de la C++98, dar are probleme de ergonomie și performanță bine-cunoscute. C++20 a introdus std::format, o bibliotecă de formatare sigură, inspirată destr.format()de la Python. C++23 a adăugat std::print, care combină formatarea și ieșirea într-un singur apel.

Înțelegerea tuturor celor trei abordări este importantă: iostream este peste tot în codul existent,std::formateste modalitatea modernă de formatare a șirurilor, iarstd::printeste viitorul ieșirii C++.

I/O clasice: cout, cin, cerr

Biblioteca iostream oferă obiecte flux pentru intrare, ieșire și ieșire de eroare standard:

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';
}
Rezultat

Apasă Rulează pentru a compila și rula acest exemplu.

Transmiteți steaguri de stat

Fiecare flux menține un set de steaguri care indică starea sa actuală:

- goodbit — Fără erori. Fluxul este pregătit pentru operațiuni I/O.
- eofbit — S-a ajuns la sfârșitul fișierului/intrării. Setați când fluxul încearcă să citească după sfârșit.
- failbit — A apărut o eroare logică (de exemplu, încercarea de a citi un număr întreg, dar întâlnirea unui text). Fluxul este încă utilizabil după apelareaclear().
- badbit — A apărut o eroare I/O irecuperabilă (de exemplu, defecțiunea discului). Fluxul este de obicei inutilizabil.

Le verificați custream.good(),stream.eof(),stream.fail(),stream.bad()sau folosind fluxul într-un context boolean:if (std::cin)este echivalent cuif (!std::cin.fail()).

Un model critic: verificați întotdeauna starea fluxului după operațiunile de introducere, nu înainte. Expresia comunăwhile (std::cin >> value)citește și verifică într-un singur pas - se oprește atunci când citirea eșuează.

Capcană

Cea mai comună eroare I/O în C++ implică amestecarea>>(extracție formatată) custd::getline(). Operatorul>>citește o valoare, dar lasă linia nouă în buffer. Unstd::getline()ulterior citește apoi acea linie nouă rămasă ca un șir gol, părând să omite intrarea.

Soluția este să apelațistd::cin.ignore(std::numeric_limits::max(), '\n')între>>șistd::getline()pentru a elimina linia nouă rămasă.

O altă abordare este să utilizațistd::getline()pentru toate intrările și să analizați singur șirurile custd::istringstreamsaustd::stoi/std::stod. Acest lucru evită complet problema și vă oferă o gestionare mai bună a erorilor.

  • După cin >> x, o nouă linie rămâne în buffer - următorul getline() îl citește ca un șir gol
  • Remediere: apelați cin.ignore(numeric_limits::max(), '\n') între >> și getline()
  • Alternativă: utilizați getline() pentru TOATE șirurile de intrare și analizați cu stoi/stod sau istringstream
  • Verificați întotdeauna starea fluxului după introducere: if (cin >> x) în loc să citiți orbește

I/O fișier

File I/O utilizează aceeași interfață de flux ca și consola I/O, custd::ifstreampentru citire șistd::ofstreampentru scriere:

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';
    }
}
Rezultat

Apasă Rulează pentru a compila și rula acest exemplu.

Formatare modernă: std::format și std::print

std::formatde la C++20 șistd::printde la C++23 oferă formatare sigură, asemănătoare Python, care este atât mai sigură, cât și mai lizibilă decât manipulatoarele 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
}
Rezultat

Apasă Rulează pentru a compila și rula acest exemplu.

Bună practică

C++ modern vă oferă trei abordări I/O. Iată când să folosiți fiecare:

Utilizațistd::print/std::println(C++23) pentru codul nou atunci când compilatorul îl acceptă. Este cea mai curată sintaxă, sigură de tip și evită problemele de manipulare cu stare a iostream.

Folosițistd::format(C++20) când aveți nevoie de un șir formatat (nu de ieșire directă) sau când compilatorul acceptă C++20, dar nu C++23.

Folosiți iostream când interacționați cu codul care așteaptă obiecte de flux, când aveți nevoie de abstracția fluxului (de exemplu, citirea din cin, fișier I/O) sau când lucrați cu baze de cod vechi.

Evitațiprintf/scanfdin C - nu sunt sigure pentru tip (trecerea unui specificator de format greșit este un comportament nedefinit) și nu funcționează cu tipurile C++ precumstd::string.

  • Preferați std::print/println (C++23) pentru noul cod de ieșire - cel mai curat și mai sigur
  • Folosiți std::format (C++20) când aveți nevoie de un șir formatat, nu de ieșire directă
  • iostream este încă necesar pentru cin, fișiere I/O și abstracții bazate pe flux
  • Evitați printf/scanf — nu sunt sigure pentru tipări și nu pot gestiona direct std::string
Idei cheie
  • Verificați întotdeauna starea fluxului după operațiunile de introducere - utilizați „if (cin >> x)” sau „while (getline(in, line))”
  • Amestecarea cin >> și getline() provoacă eroarea newline-in-buffer - apelați cin.ignore() între ele
  • Fluxurile de fișiere folosesc RAII - se închid automat atunci când ies din domeniul de aplicare
  • std::format (C++20) oferă formatare de șiruri de tip Python sigură
  • std::print/println (C++23) combină formatarea și ieșirea - modalitatea preferată pentru codul nou
  • Manipulatoarele iostream sunt cu stare (lipicioasă) — formatul std:: evită complet această problemă

Test — Verifică-ți cunoștințele

(10 XP)

1. What happens if you call`std::getline(std::cin, str)`immediately after`std::cin >> number`?

2. What does`std::format("{:#06x}", 255)`produce?

3. Why do file streams (ifstream/ofstream) automatically close when they go out of scope?