Saltar al contenido
Topos Uranos

Resumen

Esta clase cierra la séptima unidad del curso, dedicada al texto y a los archivos. Reescribe con lo aprendido el programa de la introducción, que perdía datos sin avisar y medía mal los nombres con tildes; reconstruye la cadena de conceptos que va de la cadena al formato de un archivo; separa lo que de ello vale en cualquier lenguaje de lo que es propio de C++; reúne el comportamiento indefinido de la unidad, con lo que detiene el detector y lo que no ve nadie; repite en su evaluación de salida las destrezas de la entrada; y termina con seis desafíos que combinan la unidad entera.

Objetivos de aprendizaje

  1. Procesar texto real con su codificación: predecir el tamaño en bytes de una cadena, contar sus letras y evitar las operaciones que rompen UTF-8.
  2. Leer entradas y archivos de manera robusta, distinguiendo el dato inválido del fin de los datos y validando cada dato como texto completo.
  3. Producir archivos de resultados con un formato especificado, informando cada línea inválida con su número.
  4. Reconocer el comportamiento indefinido de la unidad (la vista colgante, el índice fuera de una cadena) y distinguirlo de los fallos definidos que también pasan en silencio.
  5. Separar, en lo aprendido en la unidad, lo que vale en cualquier lenguaje de lo que es propio de C++.

Lo logrado en la unidad

La unidad se propuso enseñar a manejar el texto como dato y a hacer que un programa lea y escriba archivos sin romperse ante una entrada mal formada. El propósito se ha cumplido apoyándose solo en lo que la introducción declaró por sabido: el carácter como código de la unidad 2, los bits de la unidad 3, los vectores y los registros de la unidad 6 y las funciones con contrato de la unidad 5. El programa de la lista de notas, que leía con >>, perdía a Ana Pérez y a Luis y declaraba a «Íñigo» más largo que «Carmen», se escribe ahora así: lee las líneas de un archivo, separa la nota por el último espacio, la valida con la función parseInt de la clase Leer bien, informa la línea inválida por el flujo de errores, mide los nombres en caracteres y deja la lista en otro archivo. Para que el ejemplo sea autónomo, el programa escribe primero el archivo de datos, que en la práctica existiría ya.

Programa en C++
#include <charconv>#include <cstddef>#include <fstream>#include <iostream>#include <print>#include <string>#include <string_view>​// Poscondición: la de la clase «Leer bien la entrada».bool parseInt(std::string_view text, int& value){    int result{};    const auto last{text.data() + text.size()};    const auto [stop, error]{std::from_chars(text.data(), last, result)};    if (error != std::errc{} || stop != last) {        return false;    }    value = result;    return true;}​// Poscondición: devuelve el número de caracteres (no de bytes) de text.std::size_t letterCount(std::string_view text){    std::size_t count{0};    for (const char c : text) {        if ((c & 0xC0) != 0x80) {            ++count;        }    }    return count;}​int main(){    {        std::ofstream out{"grades.txt"};        std::println(out, "Carmen 65\nÍñigo 70\nAna Pérez 58\nLuis seis\nTomás 61");    }    std::ifstream in{"grades.txt"};    std::ofstream report{"report.txt"};    if (!in || !report) {        std::println(std::cerr, "No se pudieron abrir los archivos");        return 1;    }    std::string line{};    std::string longest{};    int lineNumber{0};    while (std::getline(in, line)) {        ++lineNumber;        const std::size_t space{line.rfind(' ')};        int grade{};        if (space == std::string::npos || !parseInt(std::string_view{line}.substr(space + 1), grade)) {            std::println(std::cerr, "Línea {} inválida: «{}»", lineNumber, line);            continue;        }        const std::string name{line.substr(0, space)};        std::println(report, "{:<10}{:>3}", name, grade);        if (letterCount(name) > letterCount(longest)) {            longest = name;        }    }    std::println("Nombre más largo: {} ({} caracteres)", longest, letterCount(longest));}
Salida
Línea 4 inválida: «Luis seis»Nombre más largo: Ana Pérez (9 caracteres)

La pantalla muestra solo el aviso y la conclusión; la lista está en el archivo, que la orden cat (en PowerShell, Get-Content) muestra después de ejecutar el programa:

Terminal
$ cat report.txtCarmen     65Íñigo      70Ana Pérez  58Tomás      61

Cada parte responde a un defecto de la introducción. La lectura por líneas y rfind permiten nombres con espacios; npos y parseInt rechazan la línea de Luis, la informan con su número y dejan seguir a Tomás; letterCount cuenta caracteres, y «Ana Pérez», con nueve caracteres y diez bytes, gana con justicia; y report.txt queda completo al terminar main, cuando su flujo se cierra.

Cadenas

La clase Cadenas presentó std::string como una secuencia de char con operaciones de texto. Enseñó a buscar con find, que devuelve una posición o npos, a comparar siempre ese valor de manera explícita, y a extraer con substr, que exige una posición válida; presentó std::string_view, segura como parámetro y peligrosa si sobrevive al texto que observa, y las cadenas de C, como contraste.

El texto no es ASCII

La clase El texto no es ASCII separó el carácter, su punto de código y sus bytes, y explicó cómo UTF-8 reparte los bits entre uno y cuatro bytes. De ello dedujo que size cuenta bytes, que los caracteres se cuentan con (c & 0xC0) != 0x80, que invertir, cambiar a mayúsculas o cortar byte a byte rompe el texto, que buscar y concatenar no lo rompen, y qué no hace la biblioteca estándar.

Leer bien

La clase Leer bien trató la entrada como frontera no confiable. Enseñó a leer líneas con std::getline, a distinguir con fail() y eof() el dato inválido, el número que no cabe y el fin de los datos, a recuperarse con clear e ignore, y a validar un número como texto con std::from_chars, exigiendo que se consuma entero; cerró con parseInt y readInt, funciones de frontera con contrato.

Archivos de texto

La clase Archivos de texto mostró que un archivo es un flujo, con el estado de std::cin. Hizo regla de la comprobación de la apertura; demostró por qué while (!in.eof()) procesa un dato de más; enseñó a escribir con std::println(out, …), reemplazando o añadiendo, y a separar avisos y resultados con std::cerr; y llevó todo a un formato de línea especificado y validado.