Перейти к содержимому
Topos Uranos

Краткое содержание

Этот урок завершает седьмой раздел курса, посвящённый тексту и файлам. Он переписывает с учётом изученного программу из введения, которая молча теряла данные и неверно измеряла имена, написанные буквами вне ASCII; восстанавливает цепочку понятий, ведущую от строки к формату файла; отделяет то, что из этого верно для любого языка, от того, что присуще C++; собирает неопределённое поведение раздела вместе с тем, что останавливает санитайзер, и тем, чего не видит никто; повторяет в итоговой проверке навыки входной; и завершается шестью задачами повышенной трудности, объединяющими весь раздел.

Цели обучения

  1. Обрабатывать реальный текст с учётом его кодировки: предсказывать размер строки в байтах, считать её буквы и избегать операций, которые ломают UTF-8.
  2. Надёжно читать ввод и файлы, отличая неверное данное от конца данных и проверяя каждое данное как цельный текст.
  3. Создавать файлы результатов в заданном формате, сообщая о каждой неверной строке с её номером.
  4. Распознавать неопределённое поведение раздела (висячее представление, индекс за пределами строки) и отличать его от определённых сбоев, которые тоже проходят молча.
  5. Отделять в изученном за раздел то, что верно для любого языка, от того, что присуще C++.

Чего достиг раздел

Раздел ставил целью научить обращаться с текстом как с данными и добиться того, чтобы программа читала и писала файлы, не ломаясь на неправильно сформированном вводе. Цель достигнута с опорой только на то, что введение объявило известным: символ как код из раздела 2, биты из раздела 3, векторы и записи из раздела 6 и функции с контрактом из раздела 5. Программа со списком оценок, которая читала с помощью >>, теряла Льва Попова и Петра и объявляла «Иван» длиннее, чем «Carmen», теперь пишется так: она читает строки файла, отделяет оценку по последнему пробелу, проверяет её функцией parseInt из урока Надёжное чтение ввода, сообщает о неверной строке через поток ошибок, измеряет имена в символах и оставляет список в другом файле. Чтобы пример был самодостаточным, программа сначала записывает файл данных, который на практике уже существовал бы.

Программа на C++
#include <charconv>#include <cstddef>#include <fstream>#include <iostream>#include <print>#include <string>#include <string_view>​// Определяет, является ли текст в точности целым числом.// Предусловие: нет.// Постусловие: то же, что в уроке «Надёжное чтение ввода».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;}​// Число символов текста в UTF-8.// Предусловие: text является корректным UTF-8.// Постусловие: возвращает число символов (не байтов) в 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Иван 70\nЛев Попов 58\nПётр шесть\nОлег 61");    }    std::ifstream in{"grades.txt"};    std::ofstream report{"report.txt"};    if (!in || !report) {        std::println(std::cerr, "Не удалось открыть файлы");        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, "Строка {} неверна: «{}»", 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, letterCount(longest));}
Вывод
Строка 4 неверна: «Пётр шесть»Самое длинное имя: Лев Попов (9 символов)

На экран выводятся только сообщение об ошибке и итог; список находится в файле, который команда cat (в PowerShell Get-Content) показывает после выполнения программы:

Терминал
$ cat report.txtCarmen     65Иван       70Лев Попов  58Олег       61

Каждая часть отвечает на один дефект введения. Чтение по строкам и rfind допускают имена с пробелами; npos и parseInt отвергают строку Петра, сообщают о ней с её номером и позволяют продолжить с Олегом; letterCount считает символы, и «Лев Попов», с девятью символами и семнадцатью байтами, выигрывает заслуженно; а report.txt оказывается полным по окончании main, когда закрывается его поток.

Строки

Урок Строки представил std::string как последовательность char с операциями над текстом. Он научил искать с помощью find, которая возвращает позицию или npos, всегда сравнивать это значение явно и извлекать с помощью substr, которая требует допустимой позиции; представил std::string_view, безопасное как параметр и опасное, если оно переживает наблюдаемый текст, и, для контраста, строки в стиле C.

Текст не сводится к ASCII

Урок Текст не сводится к ASCII разделил символ, его кодовую точку и его байты и объяснил, как UTF-8 распределяет биты по байтам, от одного до четырёх. Отсюда он вывел, что size считает байты, что символы считаются с помощью (c & 0xC0) != 0x80, что обращение, перевод в верхний регистр или обрезка байт за байтом ломают текст, что поиск и конкатенация его не ломают, и чего не делает стандартная библиотека.

Надёжное чтение ввода

Урок Надёжное чтение ввода рассматривал ввод как ненадёжную границу. Он научил читать строки с помощью std::getline, различать с помощью fail() и eof() неверное данное, число, которое не помещается, и конец данных, восстанавливаться с помощью clear и ignore и проверять число как текст с помощью std::from_chars, требуя, чтобы текст был поглощён целиком; завершился он функциями parseInt и readInt, пограничными функциями с контрактом.

Текстовые файлы

Урок Текстовые файлы показал, что файл есть поток с тем же состоянием, что и std::cin. Он ввёл правило всегда убеждаться, что файл открылся; доказал, почему while (!in.eof()) обрабатывает одно лишнее данное; научил писать с помощью std::println(out, …), заменяя или дописывая, и отделять сообщения от результатов с помощью std::cerr; и свёл всё к заданному и проверяемому формату строки.