Краткое содержание
Этот урок завершает седьмой раздел курса, посвящённый тексту и файлам. Он переписывает с учётом изученного программу из введения, которая молча теряла данные и неверно измеряла имена, написанные буквами вне ASCII; восстанавливает цепочку понятий, ведущую от строки к формату файла; отделяет то, что из этого верно для любого языка, от того, что присуще C++; собирает неопределённое поведение раздела вместе с тем, что останавливает санитайзер, и тем, чего не видит никто; повторяет в итоговой проверке навыки входной; и завершается шестью задачами повышенной трудности, объединяющими весь раздел.
Цели обучения
- Обрабатывать реальный текст с учётом его кодировки: предсказывать размер строки в байтах, считать её буквы и избегать операций, которые ломают UTF-8.
- Надёжно читать ввод и файлы, отличая неверное данное от конца данных и проверяя каждое данное как цельный текст.
- Создавать файлы результатов в заданном формате, сообщая о каждой неверной строке с её номером.
- Распознавать неопределённое поведение раздела (висячее представление, индекс за пределами строки) и отличать его от определённых сбоев, которые тоже проходят молча.
- Отделять в изученном за раздел то, что верно для любого языка, от того, что присуще C++.
Чего достиг раздел
Раздел ставил целью научить обращаться с текстом как с данными и добиться того, чтобы программа читала и писала файлы, не ломаясь на неправильно сформированном вводе. Цель достигнута с опорой только на то, что введение объявило известным: символ как код из раздела 2, биты из раздела 3, векторы и записи из раздела 6 и функции с контрактом из раздела 5. Программа со списком оценок, которая читала с помощью >>, теряла Льва Попова и Петра и объявляла «Иван» длиннее, чем «Carmen», теперь пишется так: она читает строки файла, отделяет оценку по последнему пробелу, проверяет её функцией parseInt из урока Надёжное чтение ввода, сообщает о неверной строке через поток ошибок, измеряет имена в символах и оставляет список в другом файле. Чтобы пример был самодостаточным, программа сначала записывает файл данных, который на практике уже существовал бы.
#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; и свёл всё к заданному и проверяемому формату строки.
Загрузка содержимого…