Μετάβαση στο περιεχόμενο

I/O: Ροές, Μορφοποίηση & std::print

Κάντε Master C++ I/O από το κλασικό iostream στο μοντέρνο std::format και std::print, συμπεριλαμβανομένων των καταστάσεων ροής, του I/O αρχείων και των κοινών παγίδων buffer.

Η εξέλιξη του C++ I/O

Το 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';
}
Έξοδος

Πατήστε Εκτέλεση για να μεταγλωττίσετε και να εκτελέσετε αυτό το παράδειγμα.

Ρεύμα κρατικών σημαιών

Κάθε ροή διατηρεί ένα σύνολο σημαιών πολιτείας που υποδεικνύουν την τρέχουσα κατάστασή της:

- goodbit — Δεν υπάρχουν σφάλματα. Η ροή είναι έτοιμη για λειτουργίες I/O.
- eofbit — Το τέλος του αρχείου/εισόδου έχει φτάσει. Ορίστε πότε η ροή επιχειρεί να διαβάσει μετά το τέλος.
- failbit — Παρουσιάστηκε ένα λογικό σφάλμα (π.χ., προσπάθεια ανάγνωσης ενός ακέραιου αριθμού αλλά συναντάμε κείμενο). Η ροή εξακολουθεί να μπορεί να χρησιμοποιηθεί μετά την κλήση τουclear().
- badbit — Παρουσιάστηκε μη ανακτήσιμο σφάλμα εισόδου/εξόδου (π.χ. αποτυχία δίσκου). Η ροή είναι συνήθως άχρηστη.

Μπορείτε να τα ελέγξετε με ταstream.good(),stream.eof(),stream.fail(),stream.bad()ή χρησιμοποιώντας τη ροή σε ένα boolean πλαίσιο: Τοif (std::cin)είναι ισοδύναμο με τοif (!std::cin.fail()).

Ένα κρίσιμο μοτίβο: να ελέγχετε πάντα την κατάσταση ροής μετά τις λειτουργίες εισαγωγής, όχι πριν. Το κοινό ιδίωμαwhile (std::cin >> value)διαβάζει και ελέγχει σε ένα βήμα — σταματά όταν η ανάγνωση αποτυγχάνει.

Παγίδα

Το πιο κοινό σφάλμα εισόδου/εξόδου στο C++ περιλαμβάνει την ανάμειξη του>>(μορφοποιημένη εξαγωγή) με τοstd::getline(). Ο τελεστής>>διαβάζει μια τιμή αλλά αφήνει τη νέα γραμμή στο τέλος του buffer. Στη συνέχεια, ένα επόμενοstd::getline()διαβάζει αυτή τη νέα γραμμή που έχει απομείνει ως κενή συμβολοσειρά, που φαίνεται να παραλείπει την είσοδο.

Η λύση είναι να καλέσετε τοstd::cin.ignore(std::numeric_limits::max(), '\n')μεταξύ>>καιstd::getline()για να απορρίψετε τη νέα γραμμή που έχει απομείνει.

Μια άλλη προσέγγιση είναι να χρησιμοποιήσετε τοstd::getline()για όλες τις εισόδους και να αναλύσετε μόνοι σας τις συμβολοσειρές μεstd::istringstreamήstd::stoi/std::stod. Αυτό αποφεύγει εντελώς το πρόβλημα και σας παρέχει καλύτερο χειρισμό σφαλμάτων.

  • Μετά το cin >> x, μια νέα γραμμή παραμένει στο buffer — η επόμενη getline() τη διαβάζει ως κενή συμβολοσειρά
  • Διόρθωση: καλέστε το cin.ignore(numeric_limits::max(), '\n') μεταξύ >> και getline()
  • Εναλλακτικά: χρησιμοποιήστε getline() για ΟΛΕΣ τις εισόδους και αναλύστε τις συμβολοσειρές με stoi/stod ή istringstream
  • Να ελέγχετε πάντα την κατάσταση ροής μετά την εισαγωγή: εάν (cin >> x) αντί να διαβάζετε τυφλά

I/O αρχείου

Το File 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';
    }
}
Έξοδος

Πατήστε Εκτέλεση για να μεταγλωττίσετε και να εκτελέσετε αυτό το παράδειγμα.

Modern Formatting: std::format & std::print

Τοstd::formatτου C++20 και τοstd::printτου C++23 παρέχουν ασφαλή μορφοποίηση τύπου 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
}
Έξοδος

Πατήστε Εκτέλεση για να μεταγλωττίσετε και να εκτελέσετε αυτό το παράδειγμα.

Βέλτιστη πρακτική

Modern C++ gives you three I/O approaches. Here is when to use each:

Usestd::print/std::println(C++23) for new code when your compiler supports it. It is the cleanest syntax, type-safe, and avoids the stateful manipulator problems of iostream.

Usestd::format(C++20) when you need a formatted string (not direct output), or when your compiler supports C++20 but not C++23.

Use iostream when interacting with code that expects stream objects, when you need the stream abstraction (e.g., reading from cin, file I/O), or when working with legacy codebases.

Avoidprintf/scanffrom C — they are not type-safe (passing the wrong format specifier is undefined behavior) and do not work with C++ types likestd::string.

  • Prefer std::print/println (C++23) for new output code — cleanest and safest
  • Use std::format (C++20) when you need a formatted string, not direct output
  • Το iostream εξακολουθεί να απαιτείται για αφαιρέσεις cin, 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. Γιατί οι ροές αρχείων (ifstream/ofstream) κλείνουν αυτόματα όταν ξεφεύγουν από το πεδίο εφαρμογής τους;