Aller au contenu
Topos Uranos

Résumé

Cette leçon clôt la septième unité du cours, consacrée au texte et aux fichiers. Elle réécrit avec ce qu’on a appris le programme de l’introduction, qui perdait des données sans prévenir et mesurait mal les noms à accents ; elle reconstruit la chaîne des concepts qui va de la chaîne de caractères au format d’un fichier ; elle sépare ce qui, de tout cela, vaut dans tout langage de ce qui est propre à C++ ; elle rassemble le comportement indéfini de l’unité, avec ce que le détecteur arrête et ce que personne ne voit ; elle reprend dans son évaluation de sortie les savoir-faire de l’évaluation d’entrée ; et elle se termine par six défis qui combinent l’unité entière.

Objectifs d’apprentissage

  1. Traiter du texte réel avec son codage : prédire la taille en octets d’une chaîne, compter ses lettres et éviter les opérations qui cassent l’UTF-8.
  2. Lire des entrées et des fichiers de manière robuste, en distinguant la donnée non valide de la fin des données et en validant chaque donnée comme texte complet.
  3. Produire des fichiers de résultats dans un format spécifié, en signalant chaque ligne non valide avec son numéro.
  4. Reconnaître le comportement indéfini de l’unité (la vue pendante, l’indice hors d’une chaîne) et le distinguer des échecs définis qui passent aussi en silence.
  5. Séparer, dans ce que l’unité a appris, ce qui vaut dans tout langage de ce qui est propre à C++.

L’acquis de l’unité

L’unité s’était proposé d’enseigner à manier le texte comme donnée et à faire qu’un programme lise et écrive des fichiers sans se briser devant une entrée mal formée. Le but est atteint en s’appuyant uniquement sur ce que l’introduction a déclaré tenir pour acquis : le caractère comme code de l’unité 2, les bits de l’unité 3, les vecteurs et les enregistrements de l’unité 6 et les fonctions avec contrat de l’unité 5. Le programme de la liste de notes, qui lisait avec >>, perdait Ana Pérez et Luis et déclarait « Íñigo » plus long que « Carmen », s’écrit maintenant ainsi : il lit les lignes d’un fichier, sépare la note au dernier espace, la valide avec la fonction parseInt de la leçon Bien lire, signale la ligne non valide par le flux d’erreur, mesure les noms en caractères et laisse la liste dans un autre fichier. Pour que l’exemple soit autonome, le programme écrit d’abord le fichier de données, qui, en pratique, existerait déjà.

Programme en C++
#include <charconv>#include <cstddef>#include <fstream>#include <iostream>#include <print>#include <string>#include <string_view>​// Juge si un texte est exactement un entier.// Précondition : aucune.// Postcondition : celle de la leçon « Bien lire ».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;}​// Nombre de caractères d’un texte UTF-8.// Précondition : text est de l’UTF-8 valide.// Postcondition : renvoie le nombre de caractères (et non d’octets) 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 six\nTomás 61");    }    std::ifstream in{"grades.txt"};    std::ofstream report{"report.txt"};    if (!in || !report) {        std::println(std::cerr, "Impossible d’ouvrir les fichiers");        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, "Ligne {} non valide : « {} »", 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("Nom le plus long : {} ({} caractères)", longest, letterCount(longest));}
Sortie
Ligne 4 non valide : « Luis six »Nom le plus long : Ana Pérez (9 caractères)

L’écran ne montre que le message d’erreur et la conclusion ; la liste est dans le fichier, que la commande cat (dans PowerShell, Get-Content) affiche après l’exécution du programme :

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

Chaque partie répond à un défaut de l’introduction. La lecture par lignes et rfind permettent des noms avec des espaces ; npos et parseInt rejettent la ligne de Luis, la signalent avec son numéro et laissent continuer Tomás ; letterCount compte des caractères, et « Ana Pérez », avec neuf caractères et dix octets, l’emporte à juste titre ; et report.txt est complet à la fin de main, quand son flux se ferme.

Chaînes de caractères

La leçon Chaînes de caractères a présenté std::string comme une séquence de char munie d’opérations de texte. Elle a enseigné à chercher avec find, qui renvoie une position ou npos, à comparer toujours cette valeur de manière explicite, et à extraire avec substr, qui exige une position valide ; elle a présenté std::string_view, sûre comme paramètre et dangereuse si elle survit au texte qu’elle observe, et les chaînes C, par contraste.

Le texte n’est pas ASCII

La leçon Le texte n’est pas ASCII a séparé le caractère, son point de code et ses octets, et a expliqué comment l’UTF-8 répartit les bits sur un à quatre octets. Elle en a déduit que size compte des octets, que les caractères se comptent avec (c & 0xC0) != 0x80, qu’inverser, passer en majuscules ou couper octet par octet casse le texte, que chercher et concaténer ne le cassent pas, et ce que la bibliothèque standard ne fait pas.

Bien lire

La leçon Bien lire a traité l’entrée comme une frontière non fiable. Elle a enseigné à lire des lignes avec std::getline, à distinguer avec fail() et eof() la donnée non valide, le nombre qui ne tient pas et la fin des données, à se rétablir avec clear et ignore, et à valider un nombre comme texte avec std::from_chars, en exigeant qu’il soit consommé en entier ; elle s’est close sur parseInt et readInt, fonctions de frontière avec contrat.

Fichiers texte

La leçon Fichiers texte a montré qu’un fichier est un flux, avec l’état de std::cin. Elle a fait une règle de la vérification de l’ouverture ; elle a démontré pourquoi while (!in.eof()) traite une donnée de trop ; elle a enseigné à écrire avec std::println(out, …), en remplaçant ou en ajoutant, et à séparer messages et résultats avec std::cerr ; et elle a mené le tout jusqu’à un format de ligne spécifié et validé.