Skip to content
Topos Uranos

Summary

This lesson closes the seventh unit of the course, devoted to text and files. It rewrites, with what has been learned, the program of the introduction, which lost data without warning and mismeasured names with accented letters; it reconstructs the chain of concepts that runs from the string to the format of a file; it separates what of all this holds in any language from what is specific to C++; it gathers the undefined behavior of the unit, with what the sanitizer stops and what nobody sees; it repeats in its exit assessment the skills of the entry assessment; and it ends with six challenges that combine the whole unit.

Learning objectives

  1. Process real text with its encoding: predict the size in bytes of a string, count its letters and avoid the operations that break UTF-8.
  2. Read input and files robustly, distinguishing invalid input from the end of the data and validating each item as complete text.
  3. Produce result files with a specified format, reporting each invalid line with its number.
  4. Recognize the undefined behavior of the unit (the dangling view, the index outside a string) and distinguish it from the defined failures that also go unnoticed.
  5. Separate, in what has been learned in the unit, what holds in any language from what is specific to C++.

What the unit achieved

The unit set out to teach how to handle text as data and how to make a program read and write files without breaking in the face of malformed input. The purpose has been fulfilled relying only on what the introduction declared it took for granted: the character as a code from unit 2, the bits of unit 3, the vectors and records of unit 6 and the functions with a contract of unit 5. The grade list program, which read with >>, lost Ana Pérez and Luis and declared “Íñigo” longer than “Carmen”, is now written like this: it reads the lines of a file, separates the grade at the last space, validates it with the function parseInt of the lesson Reading well, reports the invalid line through the error stream, measures the names in characters and leaves the list in another file. To make the example self-contained, the program first writes the data file, which in practice would already exist.

C++ program
#include <charconv>#include <cstddef>#include <fstream>#include <iostream>#include <print>#include <string>#include <string_view>​// Judges whether a text is exactly an integer.// Precondition: none.// Postcondition: that of the lesson “Reading well”.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;}​// Number of characters of a UTF-8 text.// Precondition: text is valid UTF-8.// Postcondition: returns the number of characters (not bytes) of 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, "Could not open the files");        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, "Invalid line {}: “{}”", 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("Longest name: {} ({} characters)", longest, letterCount(longest));}
Output
Invalid line 4: “Luis six”Longest name: Ana Pérez (9 characters)

The screen shows only the error message and the conclusion; the list is in the file, which the command cat (in PowerShell, Get-Content) shows after running the program:

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

Each part answers a defect of the introduction. Reading by lines and rfind allow names with spaces; npos and parseInt reject Luis’s line, report it with its number and let Tomás go on; letterCount counts characters, and “Ana Pérez”, with nine characters and ten bytes, wins fairly; and report.txt is complete when main ends, when its stream is closed.

Strings

The lesson Strings presented std::string as a sequence of char with text operations. It taught how to search with find, which returns a position or npos, to always compare that value explicitly, and to extract with substr, which requires a valid position; it presented std::string_view, safe as a parameter and dangerous if it outlives the text it observes, and C strings, as a contrast.

Text is not ASCII

The lesson Text is not ASCII separated the character, its code point and its bytes, and explained how UTF-8 distributes the bits among one to four bytes. From this it deduced that size counts bytes, that characters are counted with (c & 0xC0) != 0x80, that reversing, converting to uppercase or cutting byte by byte breaks the text, that searching and concatenating do not break it, and what the standard library does not do.

Reading well

The lesson Reading well treated input as an untrusted boundary. It taught how to read lines with std::getline, to distinguish with fail() and eof() invalid input, the number that does not fit and the end of the data, to recover with clear and ignore, and to validate a number as text with std::from_chars, requiring that it be consumed entirely; it closed with parseInt and readInt, boundary functions with a contract.

Text files

The lesson Text files showed that a file is a stream, with the state of std::cin. It made a rule of checking the opening; it demonstrated why while (!in.eof()) processes one item too many; it taught how to write with std::println(out, …), replacing or appending, and how to separate messages and results with std::cerr; and it brought everything to a specified and validated line format.