Ir al contenido

E/S: Streams, Formato y std::print

Domina la E/S de C++ desde el clasico iostream hasta los modernos std::format y std::print, incluyendo estados de stream, E/S de archivos y trampas comunes de buffer.

La Evolucion de la E/S en C++

La E/S de C++ ha evolucionado significativamente a lo largo de la historia del lenguaje. La clasica biblioteca iostream (std::cin, std::cout) ha sido el estandar desde C++98, pero tiene problemas bien conocidos de ergonomia y rendimiento. C++20 introdujo std::format, una biblioteca de formato segura para tipos inspirada en el str.format() de Python. C++23 agrego std::print, que combina formato y salida en una sola llamada.

Entender las tres aproximaciones es importante: iostream esta en todas partes en el codigo existente, std::format es la forma moderna de formatear cadenas, y std::print es el futuro de la salida en C++.

E/S Clasica: cout, cin, cerr

La biblioteca iostream proporciona objetos stream para la entrada estandar, la salida y la salida de errores:

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

Pulsa Ejecutar para compilar y ejecutar este ejemplo.

Flags de Estado del Stream

Cada stream mantiene un conjunto de flags de estado que indican su condicion actual:

- goodbit — Sin errores. El stream esta listo para operaciones de E/S.
- eofbit — Se ha alcanzado el fin del archivo/entrada. Se establece cuando el stream intenta leer mas alla del final.
- failbit — Ocurrio un error logico (por ejemplo, intentar leer un entero pero encontrar texto). El stream sigue siendo usable despues de llamar a clear().
- badbit — Ocurrio un error de E/S irrecuperable (por ejemplo, fallo de disco). El stream tipicamente queda inutilizable.

Puedes verificar estos con stream.good(), stream.eof(), stream.fail(), stream.bad(), o usando el stream en un contexto booleano: if (std::cin) es equivalente a if (!std::cin.fail()).

Un patron critico: siempre verifica el estado del stream despues de las operaciones de entrada, no antes. El idioma comun while (std::cin >> value) lee y verifica en un solo paso — se detiene cuando la lectura falla.

Error común

El error de E/S mas comun en C++ involucra mezclar >> (extraccion formateada) con std::getline(). El operador >> lee un valor pero deja el salto de linea final en el buffer. Un std::getline() subsiguiente entonces lee ese salto de linea sobrante como una cadena vacia, pareciendo que se salta la entrada.

La solucion es llamar a std::cin.ignore(std::numeric_limits::max(), '\n') entre >> y std::getline() para descartar el salto de linea sobrante.

Otro enfoque es usar std::getline() para toda la entrada y analizar las cadenas tu mismo con std::istringstream o std::stoi/std::stod. Esto evita el problema por completo y te da mejor manejo de errores.

  • Despues de cin >> x, un salto de linea permanece en el buffer — el siguiente getline() lo lee como una cadena vacia
  • Solucion: llama a cin.ignore(numeric_limits::max(), '\n') entre >> y getline()
  • Alternativa: usa getline() para TODA la entrada y analiza las cadenas con stoi/stod o istringstream
  • Siempre verifica el estado del stream despues de la entrada: if (cin >> x) en lugar de leer a ciegas

E/S de Archivos

La E/S de archivos usa la misma interfaz de stream que la E/S de consola, con std::ifstream para leer y std::ofstream para escribir:

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

Pulsa Ejecutar para compilar y ejecutar este ejemplo.

Formato Moderno: std::format y std::print

El std::format de C++20 y el std::print de C++23 proporcionan formato seguro para tipos, estilo Python, que es mas seguro y legible que los manipuladores de 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
}
Salida

Pulsa Ejecutar para compilar y ejecutar este ejemplo.

Buena práctica

C++ moderno te da tres enfoques de E/S. Aqui esta cuando usar cada uno:

Usa std::print/std::println (C++23) para codigo nuevo cuando tu compilador lo soporte. Es la sintaxis mas limpia, segura para tipos, y evita los problemas de manipuladores con estado de iostream.

Usa std::format (C++20) cuando necesites una cadena formateada (no salida directa), o cuando tu compilador soporte C++20 pero no C++23.

Usa iostream cuando interactues con codigo que espera objetos stream, cuando necesites la abstraccion de stream (por ejemplo, leer de cin, E/S de archivos), o cuando trabajes con bases de codigo heredadas.

Evita printf/scanf de C — no son seguros para tipos (pasar el especificador de formato incorrecto es comportamiento indefinido) y no funcionan con tipos de C++ como std::string.

  • Prefiere std::print/println (C++23) para nuevo codigo de salida — el mas limpio y seguro
  • Usa std::format (C++20) cuando necesites una cadena formateada, no salida directa
  • iostream sigue siendo necesario para cin, E/S de archivos y abstracciones basadas en streams
  • Evita printf/scanf — no son seguros para tipos y no pueden manejar std::string directamente
Puntos clave
  • Siempre verifica el estado del stream despues de las operaciones de entrada — usa 'if (cin >> x)' o 'while (getline(in, line))'
  • Mezclar cin >> y getline() causa el error del salto de linea en el buffer — llama a cin.ignore() entre ellos
  • Los streams de archivo usan RAII — se cierran automaticamente cuando salen del ambito
  • std::format (C++20) proporciona formato de cadenas seguro para tipos, estilo Python
  • std::print/println (C++23) combina formato y salida — la forma preferida para codigo nuevo
  • Los manipuladores de iostream son con estado (persistentes) — std::format evita este problema por completo

Quiz — Pon a prueba tus conocimientos

(10 XP)

1. Que sucede si llamas a `std::getline(std::cin, str)` inmediatamente despues de `std::cin >> number`?

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

3. Por que los streams de archivo (ifstream/ofstream) se cierran automaticamente cuando salen del ambito?