Riepilogo
Questa lezione chiude la settima unità del corso, dedicata al testo e ai file. Riscrive con ciò che si è imparato il programma dell’introduzione, che perdeva dati senza avvisare e misurava male i nomi con gli accenti; ricostruisce la catena dei concetti che va dalla stringa al formato di un file; separa ciò che di tutto questo vale in qualsiasi linguaggio da ciò che è proprio di C++; raccoglie il comportamento indefinito dell’unità, con ciò che il sanitizer ferma e ciò che nessuno vede; ripete nella sua valutazione d’uscita le abilità della valutazione d’ingresso; e termina con sei sfide che combinano l’unità intera.
Obiettivi di apprendimento
- Elaborare testo reale con la sua codifica: prevedere la dimensione in byte di una stringa, contarne le lettere ed evitare le operazioni che rompono UTF-8.
- Leggere input e file in modo robusto, distinguendo il dato non valido dalla fine dei dati e validando ogni dato come testo completo.
- Produrre file di risultati con un formato specificato, segnalando ogni riga non valida con il suo numero.
- Riconoscere il comportamento indefinito dell’unità (la vista pendente, l’indice fuori da una stringa) e distinguerlo dai fallimenti definiti che passano anch’essi in silenzio.
- Separare, in ciò che si è imparato nell’unità, ciò che vale in qualsiasi linguaggio da ciò che è proprio di C++.
Ciò che l’unità ha ottenuto
L’unità si era proposta di insegnare a trattare il testo come dato e a far sì che un programma legga e scriva file senza rompersi davanti a un input malformato. Lo scopo è stato raggiunto appoggiandosi solo su ciò che l’introduzione ha dichiarato di dare per scontato: il carattere come codice dell’unità 2, i bit dell’unità 3, i vettori e i record dell’unità 6 e le funzioni con contratto dell’unità 5. Il programma della lista dei voti, che leggeva con >>, perdeva Ana Pérez e Luis e dichiarava «Íñigo» più lungo di «Carmen», si scrive ora così: legge le righe di un file, separa il voto all’ultimo spazio, lo valida con la funzione parseInt della lezione Leggere bene, segnala la riga non valida attraverso il flusso degli errori, misura i nomi in caratteri e lascia la lista in un altro file. Perché l’esempio sia autonomo, il programma scrive prima il file dei dati, che in pratica esisterebbe già.
#include <charconv>#include <cstddef>#include <fstream>#include <iostream>#include <print>#include <string>#include <string_view>// Giudica se un testo è esattamente un intero.// Precondizione: nessuna.// Postcondizione: quella della lezione «Leggere bene».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;}// Numero di caratteri di un testo UTF-8.// Precondizione: text è UTF-8 valido.// Postcondizione: restituisce il numero di caratteri (non di byte) di 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 sei\nTomás 61"); } std::ifstream in{"grades.txt"}; std::ofstream report{"report.txt"}; if (!in || !report) { std::println(std::cerr, "Impossibile aprire i file"); 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, "Riga {} non valida: «{}»", 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("Nome più lungo: {} ({} caratteri)", longest, letterCount(longest));}Riga 4 non valida: «Luis sei»Nome più lungo: Ana Pérez (9 caratteri)Lo schermo mostra solo il messaggio di errore e la conclusione; la lista è nel file, che il comando cat (in PowerShell, Get-Content) mostra dopo aver eseguito il programma:
$ cat report.txtCarmen 65Íñigo 70Ana Pérez 58Tomás 61Ogni parte risponde a un difetto dell’introduzione. La lettura per righe e rfind permettono nomi con spazi; npos e parseInt respingono la riga di Luis, la segnalano con il suo numero e lasciano proseguire Tomás; letterCount conta caratteri, e «Ana Pérez», con nove caratteri e dieci byte, vince a buon diritto; e report.txt è completo al termine di main, quando il suo stream si chiude.
Stringhe
La lezione Stringhe ha presentato std::string come una sequenza di char con operazioni di testo. Ha insegnato a cercare con find, che restituisce una posizione o npos, a confrontare sempre quel valore in modo esplicito, e a estrarre con substr, che esige una posizione valida; ha presentato std::string_view, sicura come parametro e pericolosa se sopravvive al testo che osserva, e le stringhe C, come contrasto.
Il testo non è ASCII
La lezione Il testo non è ASCII ha separato il carattere, il suo punto di codice e i suoi byte, e ha spiegato come UTF-8 distribuisce i bit tra uno e quattro byte. Ne ha dedotto che size conta byte, che i caratteri si contano con (c & 0xC0) != 0x80, che invertire, passare alle maiuscole o tagliare byte per byte rompe il testo, che cercare e concatenare non lo rompono, e che cosa non fa la libreria standard.
Leggere bene
La lezione Leggere bene ha trattato l’input come frontiera non affidabile. Ha insegnato a leggere righe con std::getline, a distinguere con fail() ed eof() il dato non valido, il numero che non ci sta e la fine dei dati, a ripristinarsi con clear e ignore, e a validare un numero come testo con std::from_chars, esigendo che venga consumato per intero; si è chiusa con parseInt e readInt, funzioni di frontiera con contratto.
File di testo
La lezione File di testo ha mostrato che un file è uno stream, con lo stato di std::cin. Ha fatto una regola del controllo dell’apertura; ha dimostrato perché while (!in.eof()) elabora un dato di troppo; ha insegnato a scrivere con std::println(out, …), sostituendo o aggiungendo, e a separare messaggi e risultati con std::cerr; e ha portato tutto a un formato di riga specificato e validato.
Caricamento del contenuto…