C как узнать кодировку строки
Перейти к содержимому

C как узнать кодировку строки

  • автор:

Автоопределение кодировки текста

image

Я очень люблю программировать, я любитель и первый и последний раз заработал на программировании в далёком 1996 году. Но для автоматизации повседневных задач иногда что-то пишу. Примерно год назад открыл для себя golang. В качестве инструмента создания утилит golang оказался очень удобным. Итак.

Возникла потребность обработать большое количество (больше тысячи, так и вижу улыбки профи) архивных файлов со специальной геофизической информацией. Формат файлов текстовый, простой. Если вдруг интересно то это LAS формат.
LAS файл содержит заголовок и данные.

Данные практически CSV, только разделитель табуляция или пробелы.

А заголовок содержит описание данных и вот в нём обычно содержится русский текст. Это может быть название месторождения, название исследований, записанных в файл и пр.

Файлы эти созданы в разное время и в разных программах, доходит до того, что в одном файле часть в кодировке CP1251, а часть в CP866. Файлы эти мне нужно обработать, а значит понять. Вот и потребовалось определять автоматически кодировку файла.

В итоге изобрёл велосипед на golang и соответственно родилась маленькая библиотечка с возможностью детектировать кодовую страницу.

Про кодировки. Не так давно на хабре была хорошая статья про кодировки Как работают кодировки текста. Откуда появляются «кракозябры». Принципы кодирования. Обобщение и детальный разбор Если хочется понять, что такое “кракозябры” или “кости”, то стоит прочитать.

В начале я накидал своё решение. Потом пытался найти готовое работающее решение на golang, но не вышло. Нашлось два решения, но оба не работают.

  • Первое “из коробки”— golang.org/x/net/html/charset функция DetermineEncoding()
  • Второе библиотека — saintfish/chardet на github

Обе уверенно ошибаются на некоторых кодировках. Стандартная та вообще почти ничего определить не может по текстовым файлам, оно и понятно, её для html страниц делали.

При поиске часто натыкался на готовые утилиты из мира linux — enca. Нашёл её версию скомпилированную для WIN32, версия 1.12. Её я тоже рассмотрю, там есть забавности. Я прошу сразу прощения за своё полное незнание linux, а значит возможно есть ещё решения которые тоже можно попытаться прикрутить к golang коду, я больше искать не стал.

Сравнение найденных решений на автоопределение кодировки

Подготовил каталог softlandia\cpd тестовые данные с файлами в разных кодировках. Содержимое файлов очень короткое и одинаковое. Одна строка “Русский в кодировке CodePageName”. Дополнил файлами со смешением кодировок и некоторыми сложными случаями и попробовал определить.

Мне кажется, получилось забавно.

# Кодировка html/charset saintfish/chardet softlandia/cpd enca
1 CP1251 windows-1252 CP1251 CP1251 CP1251
2 CP866 windows-1252 windows-1252 CP866 CP866
3 KOI8-R windows-1252 KOI8-R KOI8-R KOI8-R
4 ISO-8859-5 windows-1252 ISO-8859-5 ISO-8859-5 ISO-8859-5
5 UTF-8 with BOM utf-8 utf-8 utf-8 utf-8
6 UTF-8 without BOM utf-8 utf-8 utf-8 utf-8
7 UTF-16LE with BOM utf-16le utf-16le utf-16le ISO-10646-UCS-2
8 UTF-16LE without BOM windows-1252 ISO-8859-1 utf-16le unknown
9 UTF-16BE with BOM utf-16le utf-16be utf-16be ISO-10646-UCS-2
10 UTF-16BE without BOM windows-1252 ISO-8859-1 utf-16be ISO-10646-UCS-2
11 UTF-32LE with BOM utf-16le utf-32le utf-32le ISO-10646-UCS-4
12 UTF-32LE without BOM windows-1252 utf-32le utf-32le ISO-10646-UCS-4
13 UTF-32BE with BOM windows-1252 utf-32be utf-32be ISO-10646-UCS-4
14 UTF-32BE without BOM windows-1252 utf-32be utf-32be ISO-10646-UCS-4
15 KOI8-R (UPPER) windows-1252 KOI8-R KOI8-R CP1251
16 CP1251 (UPPER) windows-1252 CP1251 CP1251 KOI8-R
17 CP866 & CP1251 windows-1252 CP1251 CP1251 unknown

Наблюдение 1

enca не определила кодировку у файла UTF-16LE без BOM — это странно, ну ладно. Я попробовал добавить больше текста, но результата не получил.

Наблюдение 2. Проблемы с кодировками CP1251 и KOI8-R

Строка 15 и 16. У команды enca есть проблемы.
Здесь сделаю объяснение, дело в том, что кодировки CP1251 (она же Windows 1251) и KOI8-R очень близки если рассматривать только алфавитные символы.

Таблица CP 1251

image

Таблица KOI8-r

image

В обеих кодировках алфавит расположен от 0xC0 до 0xFF, но там, где у одной кодировки заглавные буквы, у другой строчные. Судя по всему enca, работает по строчным буквам. Вот и получается, если подать на вход программе enca строку “СТП” в кодировке CP1251, то она решит, что это строка “яро” в кодировке KOI8-r, о чём и сообщит. В обратную сторону также работает.

Наблюдение 3

Стандартной библиотеке html/charset можно доверить только определение UTF-8, но осторожно! Пользоваться следует именно charset.DetermineEncoding(), поскольку метод utf8.Valid(b []byte) на файлах в кодировке utf-16be возвращает true.

Собственный велосипед

image

Автоопределение кодировки возможно только эвристическими методами, неточно. Если мы не знаем, на каком языке и в какой кодировке записан текстовый файл, то определить кодировку с высокой точночностью наверняка можно, но будет сложновато… и нужно будет достаточно много текста.

Для меня такая цель не стояла. Мне достаточно определять кодировки в предположении, что там есть русский язык. И второе, определять нужно по небольшому количеству символов – на 10 символах должно быть достаточно уверенное определение, а желательно вообще на 5–6 символах.

Алгоритм

Когда я обнаружил совпадение кодировок KOI8-r и CP1251 по местоположению алфавита, то на пару дней загрустил… стало понятно, что чуть-чуть придётся подумать. Получилось так.

  1. Работу будем вести со слайсом байтов, для совместимости с charset.DetermineEncoding()
  2. Кодировку UTF-8 и случаи с BOM проверяем отдельно
  3. Входные данные передаём по очереди каждой кодировке. Каждая сама вычисляет два целочисленных критерия. У кого сумма двух критериев больше, тот и выиграл.

Критерии соответствия

Первый критерий

Первым критерием является количество самых популярных букв русского алфавита.

Наиболее часто встречаются буквы: о, е, а, и, н, т, с, р, в, л, к, м, д, п, у. Данные буквы дают 82% покрытия. Для всех кодировок кроме KOI8-r и CP1251 я использовал только первые 9 букв: о, е, а, и, н, т, с, р, в. Этого вполне хватает для уверенного определения.

А вот для KOI8-r и CP1251 пришлось доработать напильником. Коды некоторых из этих букв совпадают, например буква о имеет в CP1251 код 0xEE при этом в KOI8-r этот код у буквы н. Для этих кодировок были взяты следующие популярные буквы. Для CP1251 использовал а, и, н, с, р, в, л, к, я. Для KOI8-r — о, а, и, т, с, в, л, к, м.

Второй критерий

К сожалению, для очень коротких случаев (общая длина русского текста 5-6 символов) встречаемость популярных букв на уровне 1-3 шт и происходит нахлёст кодировок KOI8-r и CP1251. Пришлось вводить второй критерий. Подсчёт количества пар согласная+гласная.
Такие комбинации ожидаемо наиболее часто встречаются в русском языке и соответственно в той кодировке в которой число таких пар больше, та кодировка имеет больший критерий.

Вычисляются оба критерия, складываются и полученная сумма является итоговым критерием.
Результат отражен в таблице выше.

Особенности, с которыми я столкнулся

Чуть коснусь прелестей и проблем, связанных с golang. Раздел может быть интересен только начинающим писать на golang.

Проблемы

Лично походил по некоторым подводным камушкам из 50 оттенков Go: ловушки, подводные камни и распространённые ошибки новичков.
Излишне переживая и пытаясь дуть на воду, прослышав от других о страшных ожогах от молока, переборщил с проверкой входного параметра типа io.Reader. Я проверял переменную типа io.Reader с помощью рефлексии.

//CodePageDetect - detect code page of ascii data from reader 'r' func CodePageDetect(r io.Reader, stopStr . string) (IDCodePage, error) < if !reflect.ValueOf(r).IsValid() < return ASCII, fmt.Errorf("input reader is nil") >. 

Но как оказалось в моём случае достаточно проверить на nil. Теперь всё стало проще

func CodePageDetect(r io.Reader, stopStr . string) (IDCodePage, error) < //test input interfase if r == nil < return ASCII, nil >//make slice of byte from input reader buf, err := bufio.NewReader(r).Peek(ReadBufSize) if (err != nil) && (err != io.EOF) < return ASCII, err >. 

вызов bufio.NewReader( r ).Peek(ReadBufSize) спокойно проходит следующий тест:

 var data *os.File res, err := CodePageDetect(data)

В этом случае Peek() возвращает ошибку.

Разок наступил на грабли с передачей массивов по значению. Немного тупанул на попытке изменять элементы, хранящиеся в map, пробегая по ним в range…

Прелести

Сложно сказать что конкретно, постоянное ли битьё по рукам от линтера и компилятора или активное использование range, или всё вместе, но практически отсутствуют залёты по выходу индекса за пределы.

Конечно, очень приятно жить со сборщиком мусора. Полагаю мне ещё предстоит освоить грабли автоматизации выделения/освобождения памяти, но пока дебильная улыбка не покидает лица.
Строгая типизация — тоже кусочек счастья.

Переменные, имеющие тип функции — соответственно лёгкая реализация различного поведения у однотипных объектов.

Странно мало пришлось сидеть в отладчике, перечитывание кода обычно даёт результат.

Щенячий восторг от наличия массы инструментов из коробки, это чудное ощущение, когда компилятор, язык, библиотека и IDE Visual Studio Code работают на тебя вместе, слаженно.

Спасибо falconandy за конструктивные и полезные советы
Благодаря ему

  1. перевёл тесты на testify и они действительно стали более читабельны
  2. исправил в тестах пути к файлам данных для совместимости с Linux
  3. прошёлся линтером — таки он нашёл одну реальную ошибку (проклятущий copy/past)

Продолжаю добавлять тесты, выявился случай не определения UTF16. Обновил. Теперь UTF16 и LE и BE определяются даже в случае отсутствия русских букв

Как распознать кодировку текста при вводе из файла?

Имеется файл с текстом, написанный в каком-то текстовом редакторе на каком-то человеческом языке.

Программа должна считывать этот текст и сортировать его строки по алфавиту. Сортировка происходит по буквам первого слова (это не суть). Чтобы правильно определить расположение символов в кодировке друг относительно друга, необходимо работать в соответствующей кодировке.

Какими способами можно распознать кодировку текста? (Visual Studio 2010)

Добавлено через 11 минут
Так, понял, что это невозможно.

Допустим, есть на входе название кодировки. Как, зная наименование кодировки, сравнить коды данных символов?

Лучшие ответы ( 1 )
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:

Распознать кодировку текста в формате СМС *.vmg
Здравствуйте. Не знаю в какой раздел форума написать. Имеются файлы *.vmg с экспортированными СМС.

Как правильно задать кодировку текста при работе с содержимым файла через TextStream?
Добрый день. Скорее всего я в очередной раз задам нубский вопрос, но я предварительно читал.

Как при вводе распознать действия и запомнить их порядок
Как при вводе распознать действия и запомнить их порядок. X\rightarrow Y \wedge Z

Кодировка текста при вводе в таблицу из файла
Здравствуйте! У меня проблемма: есть файл txt формат такой: <слово><\t><слово><\n> . и так.

Регистрация: 14.04.2012
Сообщений: 32

екстовый файл в любой кодировке — это всего лишь набор кодов символов. Поэтому определить кодировку можно только с помощью достаточно сложного анализа, основываясь как на известных признаках каждой из кодировок, так и на заранее указанных характеристиках текста в файле (например, только кириллица, исключительно осмысленный текст, а не набор символов и т.д.).

Задача, у вас, я так понимаю, учебная. И по сложности своей она гораздо уступает задаче определения кодировки. Поэтому вряд ли ваш преподаватель предполагал определение кодировки (если только это не указано явно в задании) и разумно будет просто сделать программу, нормально работающую в какой-то одной кодировке и указать это в пояснительной записке, если такая вообще требуется.

Добавлено через 7 минут
Если ставить задачу так, что кодировка указывается заранее, нужно хотя бы примерно очертить рамки списка всех возможных кодировок, которые могут быть использованы. Вот, например, из Вики список распространённых кодировок. Сами понимаете, что писать работу с каждой из них в несложной учебной задаче — не совсем разумно.

Так что повторюсь, если препод не задал именно определять кодировку, возьмите обычный поток файлового ввода ifstream, файл с набором слов буквами латинского алфавита (просто на английском языке) и без лишних символов, сделайте для такого варианта программу и не парьтесь.

Регистрация: 19.10.2014
Сообщений: 18

ЦитатаСообщение от Oleg_Ponomaryov Посмотреть сообщение

определение кодировки

Суть в том, чтобы подчинить механизм ввода условиям конкретной кодировки. Столкнулся с проблемой, пытаясь сортировать получаемые из блокнотного TXT символы.

На free pascal удалось сделать сортировку строк начинающихся с чего угодно .
вот взгляните (уберите лишнее расширение у exe)
words.txt
dictionary_sort.exe.doc

Регистрация: 14.04.2012
Сообщений: 32

Ну так а причём здесь определение кодировки? У вас есть программа, она работает с одним файлом ANSI, в котором есть как кириллица, так и латиница, выводит в такой же ANSI файл, ни определение кодировки, ни вообще разных кодировок нет. Не пробовал проверять вашу программу с файлами в других кодировках, но и смысла в этом нет — сейчас вы используете ANSI, в неё есть и русские, и английские буквы, а больше для задания, я так понимаю, и не надо. C ANSI в C++ без проблем работают потоки ifstream/ofstream и типы char/string, ничего необычного не нужно, как не нужно никакого определения кодировки — работайте себе дальше с этими ANSI файлами.

Регистрация: 19.10.2014
Сообщений: 18

Вот в том-то и проблема, что читает из блокнота нормально, печатает в блокнот — тоже по-русски.
А вот в самой программе он держит символы в массиве и сортирует их, оперируя вообще не тем, что читает и выводит )))

Вот скриншот вывода тестового проекта — тут он считал из TXT написанные подряд латинский и русский алфавиты. И вывел их на консоль. Именно с этими символами он и работает. Тут код и через равно соответствующий символ.

Кликните здесь для просмотра всего текста
Когда использовал unsigned char было то же, только с 0 до 255.

Что характерно, вывод этой кракозябры в TXT даёт верные алфавиты) Но пока оно в программе крутится, сортируются только латинские

C как узнать кодировку строки

Сообщения: 133
Благодарности: 21

Имеется массив символов типа char (язык С++). Необходимо определить кодировку этих символов (UTF8 или ANSI). Возможно ли это?

——-
«Не соглашайся ни на что, кроме совершенства!» — Анонимный автор.
«Совершенство достигается только к моменту полного краха.» — К.Н.Паркинсон.

Сообщения: 1180
Благодарности: 279

если имеется какой-нить словарик с типичными фразами, то наверное можно. Если на ascii может быть написана какая-то любая фигня, то однозначно нельзя

Для отключения данного рекламного блока вам необходимо зарегистрироваться или войти с учетной записью социальной сети.

Сообщения: 1696
Благодарности: 44

EvgeniyQQQ, Не знаю, что есть ANSI. Но в ASCII не может быть символов с кодом > 127 (это 7-итная кодировка). Если же используется «расширенный» ASCII (Latin-1 или любая другая национальная кодировка), то можно просто проверить, что исходная строка содержит символы с кодом > 127 и является корректной utf-8 строкой (т.е. удовлетворяет этим требованиям: http://tools.ietf.org/html/rfc3629#section-3). Если строка достаточно большая и не в utf-8, то где-нибудь обязательно будет неправильна закодирована, и следовательно не utf-8, иначе «произвольная однобайтовая кодировка».

Сообщения: 14
Благодарности: 4

Текст, состоящий только из символов с номером меньше 128, при записи в UTF-8 превращается в обычный текст ASCII. И наоборот, в тексте UTF-8 любой байт со значением меньше 128 изображает символ ASCII с тем же кодом. Остальные символы Юникода изображаются последовательностями длиной от 2 до 6 байтов, в которых первый байт всегда имеет вид 11xxxxxx, а остальные — 10xxxxxx. Более подробно о UTF-8 посмотрите http://ru.wikipedia.org/wiki/UTF-8. Что же касается ANSI — то насколько я понимаю, это то же, что и кодировка Windows-1251, т.е. 8-битная и содержит русские символы.

Сообщения: 4
Благодарности: 1

Конфигурация компьютера
Процессор: c2d E8200
Материнская плата: p5e
Память: 2gb
Видеокарта: gf 8800gt
ОС: 2008r2

Потребовалось мне автоопределение кодировки в текстовом файле; нашёл (не претендуя на универсальность) такой выход (builder xe3):

String tst= al+af+am+bt+sn; // несколько тэгов, выделенных из fb2 файла
if( tst[1]>0x007F && UTF8Decode(tst)[1]!=0xfffd ) al=UTF8Decode(al);// нет, это не ansi!

суть в том, что UTF8Decode от русского ansi-текста возвращает строку, забитую 0xFFFD
а от английского ansi-текста или любого utf8-текста возвращает читабельный ansi-текст.

ps для fb2, понятно, надо каждый тег проверять (т.к. в utf файле могут быть смешаны и русские и английские тэги), но принцип проверки понятен.

Кодировки

Кодировка символов — способ записи последовательности символов (строки) с помощью последовательности натуральных чисел (“кодов”). В простейшем случае кодировка задаётся таблицей символов, в которой перечислены все доступные символы, и каждому из них присвоен код (номер). Как правило, в более сложных случаях такая таблица всё равно есть, но кодировка включает в себя особенности оформления кодов с целью уменьшения размера сообщения в байтах.

ASCII

ASCII (от American Standard Code for Information Interchange — “американский стандартный код для обмена информацией”) — кодировка символов, получившая широкое распространение.

Кодировка ASCII содержит 128 символов, каждому из которых соответствует своя последовательность семи бит (двоичная запись номера символа).

Управляющие символы

Первые 32 кода и последний (символ 127) выделены для специальных управляющих символов. Большинство этих кодов были важны во времена использования телетайпов и других подобных устройств, напрямую управлявшихся последовательностью переданных на них символов, и в настоящее время практически не используются. Символ 127 ( DEL ) использовался для уничтожения содержимого перфоленты при записи поверх старого содержимого (код 127 соответствует семи пробитым отверстиям).

Некоторые управляющие символы ASCII

Номер Номер16 Символ Запись в C Смысл
0 00 null character NUL \0 нулевой символ — конец си-строки
7 07 bell BEL \a звуковой сигнал
8 08 backspace BS \b сдвинуть каретку влево на одну позицию
9 09 horizontal tabulation TAB \t сдвинуть каретку вправо до следующей колонки
10 0A line feed LF \n сдвинуть каретку вниз на одну строку (новая строка)
11 0B vertical tabulation VT \v сдвинуть каретку вниз до следующей группы строк
12 0C form feed FF \f подать новый лист
13 0D carriage return CR \r вернуть каретку на начало строки
27 1B escape ESC \x1b переключиться в режим приёма управляющей последовательности
127 7F delete DEL \x7f удалить следующий символ
  • Колонка “Запись в C” содержит обозначение символа, используемое в строковых и символьных литералах.
  • При выводе символа LF в текстовый поток вывода по стандартам C и C++ выводится последовательность, соответствующая новой строке (один символ LF в современных Unix-подобных системах или пара символов CR LF в Windows).
  • Символы VT , FF , ESC и DEL при выводе в консоль обычно или игнорируются или выводятся некоторым графическим символом.
  • Коды BS , TAB , ESC и DEL соответствуют одноимённым клавишам на стандартной “IBM PC”-клавиатуре и могут использоваться как коды, передаваемые при нажатиях этих клавиш.
  • При выводе символа BS в консоль обычно происходит перемещение курсора назад, что позволяет следующим выведенным символом затереть предыдущий. При реальной печати на бумаге смещение каретки назад могло использоваться для имитации полужирного шрифта (пропечатать один текст дважды), совмещения символов (можно получить символы вроде Ø, â, Ƚ, ǹ и т.п. — для этого такие символы как ^ , ~ располагались в шрифтах выше букв), подчёркивания (пропечатать символ _ поверх подчёркиваемого текста).

Печатные символы

Полную таблицу см. здесь.

Некоторые печатные символы ASCII

Номера Номера16 Символы Запись в C
32 20 пробел пробел
34 22 двойные кавычки «
39 27 кавычка ‘ \’
48–57 30–39 цифры 0–9 0 – 9
65–90 41–5A заглавные латинские буквы A–Z A – Z
92 5C обратная косая черта \ \\
95 5F знак подчёркивания _
97–122 61–7A строчные латинские буквы a–z a – z
  • Диапазоны цифр 0–9 и букв A–Z и a–z непрерывны, поэтому при проверке на то, является ли символ ASCII цифрой или буквой можно использовать сравнение снизу и сверху (проверку попадания в диапазон кодов).
  • Строчные и заглавные буквы отличаются установленным (строчные) или сброшенным (заглавные) пятым битом (с весом 32).

Восьмибитные кодировки

Один восьмибитный байт может принимать 256 различных значений. При передаче символов ASCII один символ кодируется одним байтом, при этом старший бит устанавливается в ноль, а семь младших бит содержат код символа. Таким образом, задействуется лишь половина доступного диапазона. Это открывает возможность расположить в следующих 128 кодах какие-то другие символы. В итоге появилось множество различных ASCII-совместимых кодировок, различающихся “верхними” (с установленным старшим битом) 128-ю символами, например, “европейская” Latin-1 и кириллические КОИ-8, IBM CP866, Windows-1251. Выбор кодировки заключается в смене различающихся 128 символов, которые называют “кодовой страницей” codepage . Интерпретатор командной строки cmd.exe в Windows позволяет переключать текущую страницу командой chcp , по умолчанию для кириллицы в консоли используется страница 866 (совместимость с MS-DOS).

К сожалению, такие восьмибитные кодировки не могли решить проблему как кодирования письменностей, в которых большое количество знаков (например, китайская) или записи текстов, включающих символы из разных письменностей.

Юникод

Юникод Unicode — группа стандартов, посвящённых кодированию и передаче текстов на электронных носителях в символьной форме. Фактически состоит из двух частей: стандартизованного набора знаков (Universal Character Set, UCS) и кодировок (Unicode Transformation Format, UTF). UCS основан на символьном подходе в противовес графическому подходу, т.е. даже если символы из разных письменностей выглядят одинаково, они считаются разными (например, латинское C и кириллическое С). UCS разбит на пронумерованные “плоскости” planes , собирающие символы в группы, но нумерация самих символов сплошная. Первые 128 символов совпадают с кодировкой ASCII. Ряд “символов” UCS представляет собой не отдельные символы, а части символов, графически комбинируемые с соседними символами. Кроме того, есть и управляющие символы, в том числе, доставшиеся в наследство от ASCII. Поэтому элементы UCS принято называть кодовыми позициями codepoints . Зарезервированный объём UCS составляет немногим больше одного миллиона символов, используемый — всего порядка 110 тысяч. Недостатком Юникода является то, что одно и то же изображение часто может быть получено различными способами (помимо визуально совпадающих знаков различных письменностей, это могут быть символы-комбинации знаков, например, буквы й и ё).

Кодировка UTF-32 (UTF-32BE для BE-порядка байт, UTF-32LE для LE-порядка байт) передаёт каждую кодовую позицию 4-байтным целым (номером в UCS, поэтому UTF-32 также обозначают как UCS-4). Эта кодировка сравнительно удобна для работы с Юникод-текстами в памяти, но считается недостаточно компактной для задач хранения и передачи текстов. К сожалению, даже в случае UTF-32 одному знакоместу (графическому изображению символа) на экране может соответствовать больше одного 4-байтного кода, его формирующих.

Кодировка UTF-8 передаёт каждую кодовую позицию последовательностью байт (от 1 до 4). При этом, текст в кодировке ASCII является текстом в кодировке UTF-8 (обратная совместимость), а для представления дополнительных символов используются байты с установленным старшим битом. Данная кодировка на данный момент является самым распространённым способом передачи текстов по сети Интернет. Её преимуществами, в частности, являются компактность представления символов ASCII и независимость от порядка байт (нет проблемы big endian vs little endian).

Тема работы с Юникодом (и строковыми данными вообще) очень обширна и содержит множество тонкостей, здесь рассматриваются лишь некоторые простые элементы в аспекте программирования на C и C++. Для серьёзного изучения как Юникода, так и затронутых здесь разделов стандартной библиотеки C++ следует обратиться к специализированным источникам.

Типы символов в C++

В языке C первоначально был только один встроенный тип, предназначенный для представления символов — тип char , “по совместительству” являющийся представлением байта, как минимальной отдельно адресуемой единицей памяти. Символьные кодировки не входят в стандарты C и C++ и являются прерогативой системы.

В начале развития стандарта Юникод в C++ был введён новый тип символа — wchar_t (т.е. “широкий символ” wide character ), по определению являющийся целочисленным типом с диапазоном, достаточным для представления всех символов, доступных на данной системе. Так как поначалу предполагалось, что Юникоду “хватит” 16 бит для представления всех номеров UCS, в некоторых системах (например, Windows) был закреплён 16-битный wchar_t. В современных GNU/Linux-системах используется 32-битный wchar_t, подходящий для кодировки UTF-32.

В связи с введением в обиход различных кодировок UTF и отсутствием совместимости между различными системами при использовании wchar_t в C++11 были добавлены два новых символьных (целочисленных) типа: char16_t для 16-битных кодировок (UCS-2, UTF-16) и char32_t для 32-битных кодировок (UTF-32). Возможно введение в будущем стандарте типа char8_t , пока же для представления единиц кодирования строк в кодировке UTF-8 используется тип char.

Литералы

Символьные литералы

Символьный литерал — представление константы символьного типа непосредственно в тексте программы. Символьные литералы записываются в одинарных кавычках и должны содержать один символ, который задаётся либо непосредственно, либо с помощью специальной экранированной последовательности escape sequence , открываемой символом \ .

Стандартные экранированные последовательности для некоторых управляющих символов, а также специальных символов (обратная косая черта, кавычки) приведены в таблицах выше в колонках под заголовком “Запись в C”.

Экранированные последовательности позволяют указать код символа числом: в шестнадцатеричной системе после префикса x или в восьмиричной системе (без префикса), например, ‘\x5c’ — то же, что ‘\134’ — то же, что ‘\\’ , а именно сам символ “обратная косая черта”.

При записи непосредственно символа в литерал, компилятор может оставить код в той кодировке, в которой символ был введён, либо преобразовать в некоторую кодировку “по умолчанию”. Например, исходный код может быть набран в кодировке UTF-8, в которой кириллические знаки записываются двухбайтными кодами, но компилятор Visual C++ при оформлении символьного литерала типа char (один байт) преобразует их к кодировке Windows-1251.

Выбор конкретного типа символа осуществляется с помощью префикса.

Префиксы символьных литералов

Тип Префикс Пример Стандарт Диапазон
char нет ‘a’ C90 системная кодировка
wchar_t L L’ф’ C++98 системная кодировка
char16_t u u’ф’ С++11 UCS-2
char32_t U U’∀’ C++11 UCS-4
char8_t u8 u8’z’ C++17 (план) ASCII

Строковые литералы

Строковый литерал — представление константной последовательности символов непосредственно в тексте программы. Строковые литералы записываются в двойных кавычках и могут не содержать ни одного символа. Максимальное допустимое количество символов с строковом литерале определяется компилятором.

Каждому строковому литералу соответствует константный массив соответствующего символьного типа, содержащий символы литерала в соответствующей символьному литералу кодировке плюс завершающий нулевой символ. Поэтому реальный размер такого массива, по крайней мере, на единицу больше числа символов в нём.

Строковый литерал может содержать такие же экранированные последовательности, как символьный литерал. Каждой экранированной последовательности соответствует один символ.

Расположенные подряд строковые литералы, разделённые лишь пробельными символами и комментариями, автоматически конкатенируются в один строковый литерал. Например:

// Выводится одна строка, а не три. cout "First line\n" // \n должны быть выставлены явно "Second line\n" "Ok, finally the third line.";
Префиксы строковых литералов

Префикс Пример Кодировка Тип символа Стандарт
нет «two words» системная char C90
L L»два слова» системная wchar_t C++98
R R»!(C:\User\)!» см. ниже см. ниже C++11
u u»∀ε>0 ∃δ « UTF-16 char16_t C++11
U U»A∨¬A» UTF-32 char32_t C++11
u8 u8″escape-знак» UTF-8 char8_t C++11

Префикс R (от англ. raw — “сырой”) позволяет ввести строку без экранирования символов: все символы между R“ разделитель ( и ) разделитель ” включаются в литерал. Здесь разделитель — произвольная последовательность символов, которая выбирается так, чтобы она (с закрывающей скобкой) не встречалась в самой строке. Перед префиксом R можно поставить префикс, определяющий тип символа и кодировку.

Стандартный заголовочный файл cstring

Си-строка или нуль-терминированная строка — структура данных, представляющая собой массив символов, последний из которых (и только он) является нулевым. Так как нулевой символ маркирует конец си-строки, для передачи её в функцию достаточно передать указатель на её начало. Любой указатель на символ некоторой си-строки является указателем на её подстроку — тоже си-строку.

Заголовочный файл cstring содержит базовый функционал для работы с си-строками и массивами символов (байт). Массивы байт передаются парой (указатель (типа void* ), размер (в байтах)). Далее приведены краткие описания некоторых стандартных функций, объявленных в этом заголовочном файле.

Для краткости даны объявления функций только для языка C. В случае C++ нередко существуют аналогичные пары функций с различной константностью (C “теряет” константность), например, в C:

  • void* memchr (const void* mem, int byte, size_t count) — теряет свойство “только для чтения” блока mem .

В то же время в C++ (в пространстве имён std) имеется сразу два варианта:

  • void* memchr (void* mem, int byte, size_t count) — блок, допускающий запись;
  • const void* memchr (const void* mem, int byte, size_t count) — блок только для чтения.

Блоки памяти

  • void* memset (void* dest, int byte, size_t count) заполняет блок памяти с адресом dest длиной count байт значением byte (приведённым к unsigned char ). Возвращает dest .
  • void* memcpy (void* dest, const void* src, size_t count) копирует содержимое блока памяти с адресом src длиной count байт по адресу dest . Возвращает dest . В случае, если диапазоны [ src , src + count ) и [ dest , dest + count ) имеют непустое пересечение, поведение не определено.
  • void* memmove (void* dest, const void* src, size_t count) действует аналогично memcpy , но также обрабатывает случай пересекающихся диапазонов. Семантика в этом случае эквивалентна копированию через временный промежуточный буфер. Наличие двух разных функций memcpy и memmove связано с тем фактом, что memcpy может быть быстрее благодаря предположению, что диапазоны не пересекаются.
  • void* memchr (const void* mem, int byte, size_t count) ищет в блоке памяти с адресом mem длиной count байт первое с начала блока вхождение байта byte (приводится к unsigned char ). Если такой байт найден, то функция возвращает его адрес. В противном случае возвращает нулевой указатель.
  • int memcmp (const void* lhs, const void* rhs, size_t count) сравнивает побайтно лексикографически (“словарно”) два блока памяти (с адресами lhs и rhs ) одинаковой длины count байт. Возвращает –1, если блок lhs стоял бы в словаре до rhs ; 0, если блоки равны; и +1, если бы блок lhs стоял бы в словаре после rhs . При сравнении порядок байт относительно друг друга определяется их числовыми значениями, а не смыслом соответствующих им символов.

Си-строки

Все эти функции порождают неопределённое поведение, если в качестве параметра, подразумевающего передачу адреса си-строки с завершающим нулём, передаётся указатель, не указывающий на си-строку.

  • size_t strlen (const char* str) возвращает длину си-строки с адресом str , т.е. по сути расстояние от str до первого вхождения нулевого символа, измеренное в байтах.
  • char* strcpy (char* dest, const char* src) копирует строку с адресом src (вместе с завершающим нулём) в буфер по адресу dest , возвращает dest . Поведение не определено, если выделенный буфер недостаточно большой (выделен слишком маленький блок памяти, либо диапазон области копирования пересекается с исходной строкой).
  • char* strcat (char* dest, const char* src) дописывает копию си-строки по адресу src к си-строке по адресу dest . Т.е. выполняет конкатенацию строк. Блок памяти, в котором хранится строка по адресу dest должен иметь достаточный размер, чтобы вместить конкатенацию. Возвращает dest .
  • char* strchr (const char* str, int byte) ищет в си-строке по адресу str первое вхождение байта со значением byte . Возвращает указатель на найденный байт, либо нулевой указатель, если найти заданный байт не удалось.
  • char* strrchr (const char* str, int byte) ищет в си-строке по адресу str последнее вхождение байта со значением byte . Возвращает указатель на найденный байт, либо нулевой указатель, если найти заданный байт не удалось.
  • int strcmp (const char* lhs, const char* rhs) сравнивает две си-строки лексикографически, возвращает –1, если строка lhs стояла бы в словаре до rhs ; 0, если строки равны; +1, если бы lhs стояла бы в словаре после rhs . При сравнении порядок байт относительно друг друга определяется их числовыми значениями, а не смыслом соответствующих им символов.
  • char* strstr (const char* str, const char* substr) ищет в си-строке по адресу str первое вхождение подстроки, заданной си-строкой с адресом substr . Возвращает указатель на первый символ найденной подстроки, либо нулевой указатель, если такой подстроки нет.

Также существуют аналоги данных функций для работы с wchar_t и многобайтными кодировками.

Стандартный заголовочный файл string

Возможности C++ позволяют определить тип данных, объекты которого самостоятельно управляют массивом символов (строкой) и предоставляют набор удобных в использовании базовых операций.

Стандартные строковые типы

Тип строки Тип элемента Стандарт
string char C++98
wstring wchar_t C++98
u16string char16_t C++11
u32string char32_t C++11

Все перечисленные строковые типы по существу устроены одинаково и предоставляют одинаковые с точностью до типа данных наборы операций. Фактически они являются объектами, управляющими динамическими массивами символов, в которых и хранятся строки. В отличие от си-строк, данные объекты позволяют хранить произвольные символы, включая нулевые. Строки можно сравнивать с помощью обычных операций сравнения вроде == и < (используется лексикографический порядок).

Простейшие операции с объектами string

Далее приведены примеры использования некоторых возможностей стандартных строк.

// Переменную строкового типа можно инициализировать литералом соответствующего типа // или просто нуль-терминированной си-строкой (указателем на char). string s = "text"; // В C++14 с помощью суффикса s можно явно указать, // что строковый литерал задаёт объект string // (или аналогов в случае наличия префиксов). string with_null_chars = "\0here is some text\0"s; cout // попробуйте сравнить с выводом в случае отсутствия суффикса s // При присваивании переменных строки копируются. s = with_null_chars; assert(s == with_null_chars); // К символам (кодовым позициям) в строке можно обращаться по индексу как к массиву. s[0] = '!'; // заменили символ в позиции 0 в копии assert(s != with_null_chars); // Строки "знают" свой размер: assert(s.size() == 19); // Прямая проверка на пустоту (т.е. на равенство размера нулю): assert(!s.empty()); // Очистить строку можно 1) присвоив "", 2) вызвав clear: with_null_chars.clear(); assert(with_null_chars.empty()); // Указатель на массив символов, принадлежащий объекту строки, в формате си-строки // (например, чтобы задействовать функции из стандартной библиотеки C) можно получить // с помощью функции c_str: assert(strchr(s.c_str(), 'h') != nullptr); // Размер строки можно изменять: при уменьшении лишние с конца символы удаляются, // при увеличении в конец строки нужное количество раз добавляется заданный символ. s.resize(3); assert(s == "!he"); s.resize(5, 'e'); // если не указать символ, то будет добавлен нулевой символ assert(s == "!heee"); // Конкатенация через + и +=, может добавлять символы и литералы. s += 'w'; assert(s == "!heeew"); // Извлечь копию подстроки с заданной позиции и с заданной длиной можно функцией substr: assert( (s.substr(1, 2) + "at") == "heat" );

В заголовочном файле string также определены удобные функции преобразования между числами и их строковыми представлениями:

  • stoi, stol, stoll, stoul, stoull (const ( w ) string &s, size_t *pos = nullptr, int radix = 10) распознают содержимое строки s как целое число (типов int, long, long long, unsigned long и unsigned long long соответственно — два последних варианта не принимают знак числа), читая её с начала до момента первой ошибки (или конца числа). По адресу pos (если это не нулевой указатель) сохраняется индекс первого символа за считанным числом. Значение radix задаёт основание системы счисления (принимаются основания от 2 до 36). Если в качестве значения radix передать 0, то основание будет распознаваться по префиксу аналогично целочисленным литералам (0 — основание 8, 0x/0X — 16, другое — 10; двоичные 0b/0B пока не вошли в стандарт по этим функциям, так как они определены через стандартные функции C strtol, strtoll, strtoul и strtoull, а в стандарт C двоичные литералы пока не входят).
  • stof, stod, stold (const ( w ) string &s, size_t *pos = nullptr) распознают содержимое строки s как число с плавающей точкой (типов float, double и long double соответственно), читая её с начала до момента первой ошибки (или конца числа). По адресу pos (если это не нулевой указатель) сохраняется индекс первого символа за считанным числом. Числа распознаются в соответствии с правилами записи литералов, кроме того, принимаются строки INF или INFINITY (без учёта регистра) как значение “бесконечность” и NAN как значение “нечисло”.
  • to_string, to_wstring ( что-то ) возвращает строковое представление (в виде объекта string и wstring соответственно) значений встроенных типов.
assert(stoi(to_string(10)) == 10); assert(stod(to_string(3.14)) == 3.14);

Создание строк

Класс string предоставляет набор конструкторов, некоторые из которых могут пригодиться, например, при выполнении заданий из самостоятельной 11.

Примеры даны в двух вариантах: определение переменной типа string и создание временного объекта типа string.

// Конструктор по умолчанию создаёт пустую строку. string s1; cout // создать пустую строку в выражении. // Конструктор, создающий строку повторением символа заданное число раз. string s2(5, '*'); cout 5, '*'); // выведет ***** // Конструктор из си-строки. string s3("nevermore!"); // или s3 = "nevermore!" // Конструктор из суффикса другой строки (только для string, не си-строки!). string s4(s3, 5); // 5 -- позиция первого символа префикса. cout 5); // выведет more! // Конструктор из подстроки другой строки. string s5(s3, 5, 4); // 5 -- позиция начала, 4 -- длина подстроки. cout 5, 4); // выведет more // Конструктор из массива символов: надо передать указатель и размер. char block[] < 1, 2, 3, 4 >; string s6(block, sizeof(block)); cout sizeof(block)); // что выведет? // Конструктор из диапазона символов, заданного парой итераторов. string s7(istream_iteratorchar>, istream_iteratorchar><>); cout << string(istream_iteratorchar>, istream_iteratorchar><>); // -- прочитать весь ввод с cin, пропуская пробелы, и собрать в строку.

Вставка, удаление и замена подстрок

Вставка подстрок может осуществляться с помощью функций append (в конец) и insert (на произвольную позицию).

Удаление подстроки может быть выполнено функцией erase .

Замена подстрок (с заданным положением, а не содержанием) выполняется с помощью функции replace .

// Вставка и удаление string alpha = "al"; alpha.append("pha"); // то же, что alpha += "pha". assert(alpha == "alpha"); // Добавить префикс заданной длины из си-строки. alpha.append("same", 1); assert(alpha == "alphas"); // Добавить подстроку объекта string с заданной позиции заданной длины. string sigma = "sigma"; alpha.append(sigma, 1, 3); assert(alpha == "alphasigm"); // Добавить заданное количество символов. alpha.append(2, '+'); // сначала количество, потом символ. assert(alpha == "alphasigm++"); // Удалить с заданной позиции заданное число символов. alpha.erase(0, 4); // с нуля == удалить префикс. assert(alpha == "asigm++"); // Удалить суффикс с заданной позиции. alpha.erase(2); assert(alpha == "as"); // Вставить заданное число символов в заданную позицию. alpha.insert(1, 4, '*'); assert(alpha == "a****s"); // Вставить заданную строку в заданную позицию. alpha.insert(3, sigma); assert(alpha == "a**sigma**s"); // Или подстроку другой строки. sigma.insert(2, alpha, 0, 3); assert(sigma == "sia**gma"); // Удалить всё содержимое строки. alpha.clear(); // то же, что alpha.erase() или alpha = "". assert(alpha == ""); // Замена подстрок string word = "orthographia"; // Заменить подстроку с заданной позицией и длиной на другую строку. word.replace(5, 5, "dox"); assert(word == "orthodoxia"); string other = "Sparax"; // Заменить подстроку на подстроку другой строки. // (Эффективность выше, чем при использовании substr). word.replace(0, 5, other, 1, 4); assert(word == "paradoxia"); // В качестве длины второй подстроки можно указать специальное значение string::npos, // в этом случае в качестве подстроки будет взят весь остаток строки. word.replace(0, 4, other, 1, string::npos); assert(word == "paraxdoxia"); // string::npos можно указать и в качестве длины заменяемой подстроки, // тогда будет осуществлена замена всего остатка (суффикса, "хвоста") строки. word.replace(5, string::npos, other, 1, string::npos); assert(word == "paraxparax"); // Вместо строки можно указывать символ, которым будет затёрта заданная подстрока. // Третий параметр -- число символов (т.е. подстрока длины 4 заменяется на три звёздочки). word.replace(3, 4, 3, '*'); assert(word == "par***rax");

Поиск в строке

Объекты string поддерживают несколько функций поиска (подстрок и символов). Все эти функции возвращают индекс первого символа, отвечающего найденному вхождению, либо специальное значение string::npos , если ничего не было найдено.

string text = "alpha beta gamma delta epsilon alpha"; // Можно искать другую строку как подстроку. assert(text.find("beta") == 6); assert(text.find("zeta") == string::npos); // Можно искать, начиная не с начала, а с заданной позиции, // что удобно для нахождения всех вхождений подстроки. assert(text.find("beta", 7) == string::npos); assert(text.find("alpha") == 0); assert(text.find("alpha", 10) == 31); // Если дан массив символов, а не си-строка, то следует указать его длину // дополнительным параметром. char delta[] = < 'd', 'e', 'l', 't', 'a' >; assert(text.find(delta, 0, 5) == 17); // Наконец, можно искать конкретный символ. assert(text.find('a') == 0); assert(text.find('z') == string::npos); assert(text.find('a', 1) == 4); // Аналогичный find набор возможностей предоставляет функция // rfind, // её отличие заключается в том, что она ищет // последнее (самое правое) вхождение вместо первого (самого левого). // При этом позиция начала поиска исключает из поиска правую часть // строки (суффикс), а не левую (префикс) как в случае find. assert(text.rfind("alpha") == 31); assert(text.rfind("alpha", 10) == 0); // text = "alpha beta gamma delta epsilon alpha" // Если надо найти самое левое вхождение некоторого символа // из заданного набора, то следует воспользоваться функцией // find_first_of assert(text.find_first_of("bcd") == 6); assert(text.find_first_of("bcd", 7) == 17); // Напротив, если требуется найти символ, НЕ принадлежащий // заданному набору, то следует воспользоваться функцией // find_first_not_of assert(text.find_first_not_of("abcdef") == 1); assert(text.find_first_not_of("abcdef", 10) == 10); // пробел // Наконец, можно искать не слева направо, а справа налево. // Соотвественно: find_last_of и find_last_not_of assert(text.find_last_of("bcd") == 17); assert(text.find_last_not_of("abcdef") == 34); // h

Элементы библиотеки Iostreams

Базовые элементы

Вывод текстового представления значения value в stdout выполняется с помощью оператора

std::cout 
// Вариант 1, получить символ как результат функции, // если считать символ невозможно, возвращает специальное значение EOF. // EOF является целым числом вне диапазона значений кодов символов, // поэтому его присваивание переменной ch является, вообще говоря, ошибкой. // Данный вариант используется только если достоверно известно, что поток не кончился. ch = cin.get(); // Вариант 2, если возможно, считать и записать символ в переменную, // переданную по ссылке. В противном случае не изменять эту переменную. // Здесь функция get возвращает исходный объект потока (в данном случае сам cin), // предварительно выставив флаги ошибок, если они произошли. if (cin.get(ch)) // Если не было ошибок, cout.put(ch); // вывести то, что удалось считать.

Если использовать для считывания символов вместо функции get оператор >> , то поток будет пропускать все пробельные символы. Проверьте, например, как работает следующий код:

// Типичная идиома для считывания последовательности: цикл for. // Результатом выражения (cin >> ch) является сам cin, // поэтому данный цикл выполняется до первой ошибки ввода (или конца ввода). for (char ch; cin >> ch;) cout.put(ch);

Считать “слово”, т.е. пропустить все пробельные символы и считать последовательность непробельных символов до первого пробельного или конца файла (сочетание +-. тоже будет считаться “словом”) можно с помощью std::cin >> word , где word имеет тип string .

Считать всё до перевода строки можно с помощью std::getline(std::cin, line) , где line — тоже строковая переменная. Чтобы читать из потока до заданного символа ch , можно указать третий параметр: std::getline(std::cin, line, ch) . Функция getline забирает символ-разделитель (перевод строки или значение ch ) из потока ввода, но не дописывает его к считанной строке.

Далее идёт листинг, демонстрирующий некоторые возможности средств текстового ввода-вывода стандартной библиотеки C++. Возможно, не всё из этого будет понятно на данном этапе, однако, оно может пригодиться в дальнейшем.

// Считать строчку до перевода строки: string text; getline(cin, text); // Считать (один) следующий символ: char ch = cin.get(); // Положить обратно символ вместо ранее считанного // (вместо ch мог стоять любой символ): cin.putback(ch); // Положить назад именно тот символ, что был считан: cin.unget(); // Получить следующий символ, не убирая его из потока: ch = cin.peek(); // Считать следующий непробельный символ // (пробельные символы пропускаются): cin >> ch; // Проверить был ли достигнут конец файла: bool ended = cin.eof(); // Проверить были ли ошибки ввода (например, пытались считать число, а в потоке были буквы): bool failed = cin.fail(); // Проверить внешние ошибки (например, привязанный к stdin файл был удалён системой во время работы // или произошёл сбой оборудования, кроме того, данный бит ошибки устанавливается при некоторых недопустимых действиях программы): bool external_error = cin.bad(); // Наоборот, проверить, что ошибок не было: bool ok = cin.good(); // Сбросить флаги ошибок, в том числе признак конца файла: cin.clear(); // Вывести все символы буфера потока на конечное устройство (очистить буфер): cout.flush(); // Вывести перевод строки и вызвать flush(): cout // Передать в поток один символ с заданным кодом: char ch = 111; cout.put(ch); // -- или -- (отличия более тонкие, чем в случае с вводом): cout /* Может выполнить "расширение" символа, чтобы соответствовать особым требованием ОС, например, в Windows перевод строки оформляется парой символов с кодами 13 и 10. */ // Выбрать количество знаков после точки при выводе // чисел с плавающей точкой: cout.precision(3); // точность -- три значащих цифры cout 1.12345; // > 1.12

“Задержка экрана” в простых случаях может быть выполнена с помощью чтения символа с cin (функции get и ignore ). Если буфер ввода пуст, то исполнение программы остановится до тех пор, пока пользователь не введёт что-нибудь.

// Считать один символ и отбросить его: cin.get(); // Название функции ignore подчёркивает, что символ не нужен. cin.ignore(); // Если надо пропустить известное число символов, то можно передать его функции ignore: cin.ignore(10); // отбросить 10 символов

К сожалению, такой нехитрый приём не работает в ряде случаев, когда поток ввода содержит неопределённое количество “мусора”. Более универсальный метод представлен в примере по ссылке.

Использование fstream

// Подключить файловые потоки ввода-вывода: #include 
// Создать объект потока-ввода: ifstream f; f.open("my.txt"); // открыть файл с именем "my.txt" // Создать объект потока ввода, читающий файл filename (переменная типа string или char*): ifstream f(filename); f.close(); // закрыть файл // Создать объект потока вывода, пишущий файл filename (заново): ofstream f(filename); // Либо открыть для дописывания (не уничтожая старое содержимое): ofstream f(filename, ios::app); // app от append // Проверить, открыт ли файл: if (f.is_open()) cout "f is opened"; // Проверить, что флаги ошибок сброшены: if (f) // проверка также пройдёт успешно, если мы не пытались открыть файл cout "f is good";

В целом, можно считать, что объекты ifstream ведут себя как cin, а объекты ofstream ведут себя как cout.

Файл позволяет произвольно изменять позицию чтения (для ifstream) и позицию записи (для ofstream).

// Узнать текущую позицию записи в файле // (== расстоянию в байтах от начала файла): auto f_pos = f.tellp(); // Установить позицию записи после первых 10 байт: f.seekp(10);

Для позиции чтения существуют аналогичные функции tellg и seekg . Функции seekg и seekp кроме варианта с абсолютным смещением имеют вариант со смещением относительно трёх возможных позиций: начала ios::beg , текущей позиции ios::cur и конца ios::end . Это позволяет, например, узнать размер открытого файла:

// Тип streampos -- целочисленный тип, достаточный для того, чтобы представлять позицию или смещение в файле. // Установим позицию в конец файла и узнаем, чему она равна -- это и будет размер файла. // Данная функция работает не только для файлов. streampos file_size(istream &s) < // Сохраним текущую позицию, чтобы восстановить её после измерения размера. const auto old_pos = s.tellg(); s.seekg(0, ios::end); // Смещение 0 от конца. const auto size = s.tellg(); s.seekg(old_pos); // Вернуться на исходную позицию. return size; >

Допускается открытие одного и того же файла сразу несколькими объектами ifstream. Каждый из них будет читать данные со своей позиции чтения.

Использование sstream

// Подключить потоки вывода в строку. #include 
// Допустим, в fn находится фрагмент имени некоторого файла: string fn = "dataset.bin"; // Нужно открыть файл с именем fn, дополненным "." и номером n. // Поток вывода в строку (хранится внутри) позволит "собрать" нужное имя. ostringstream newfn; // Сформировали нужную строку: newfn '.' // str() возвращает строку из объекта потока, можно открыть файл: ofstream outf(newfn.str()); // С другой стороны, конкретно этот пример можно записать короче // с помощью функции to_string, появившейся в C++11: ofstream outf(fn + "." + to_string(n));

Аналогично, можно считать, что объекты istringstream ведут себя как cin, а объекты ostringstream ведут себя как cout. Функция-член str() позволяет обращаться к содержимому потока как к строке типа string: str() возвращает копию содержимого потока в виде строки, а str(строка) устанавливает новое содержимое потока.

// Чтение последовательности векторов через istringstream. // Каждый вектор занимает свою строку, поэтому читаем строку в istringstream, // а затем уже из него читаем элементы вектора. // Прочитать вектор из потока. template class X> auto read_vector(istream &is) < vectordata; for (X x; is >> x;) data.push_back(x); return data; > // Прочитать набор векторов из потока. template class X> auto read_vectors(istream &is) < vector data; istringstream line_input; for (string line; getline(is, line);) < line_input.str(line); // читать из строки line. data.push_back(read_vector(line_input)); > return data; >

В случае записи “поверх” уже записанных данных потоки вывода (и ostringstream и ofstream) просто затирают символы на позиции записи. Вставка символов или удаление “хвоста” не осуществляются.

#include #include int main() < using namespace std; ostringstream s; s "abcdefg"; s.seekp(0); s.put('x'); cout // > xbcdefg return 0; >

Тем не менее, при выводе текста в консоль обычно можно имитировать удаление символов с помощью вывода символа BS . Например “крутящаяся палочка”:

while (true) std::cout .put('|').flush().put('\b') .put('/').flush().put('\b') .put('-').flush().put('\b') .put('\\').flush().put('\b');

Впрочем, данное действие довольно бессмысленно. Если мы хотим показать, что идёт некий процесс, можно выполнять небольшую часть работы между сменами знака “палочки”. В примере ниже функция do_next_job выполняет некоторый фрагмент задачи и возвращает true, если надо продолжить ожидание, и false, если работа завершена:

static char sprite[] = "|/-\\"; for (unsigned s = 0;; s = (s + 1) % 4) < std::cout.put(sprite[s]).flush(); if (!do_next_job()) break; std::cout.put('\b'); >

iostream-типы

Существует набор стандартных типов, механически сочетающих в себе интерфейсы потока ввода istream и потока вывода ostream. В частности, они предоставляют две независимых позиции: позицию чтения и позицию записи. С их помощью можно работать с файлом (тип fstream ) или строкой (тип stringstream ) как с расширяемым массивом байт, который можно и читать и изменять.

Пример использования stringstream для тестирования функции, читающей данные из потока в примере 0520-euclid_norm_2.cpp:

bool test_euclid_norm_stream() < stringstream ts; ts 4 ' ' 1 ' ' 0 ' ' 3 ' '; ts 0 ' ' -1 ' ' -2 ' ' -6; if (!almost_equal(euclid_norm(ts), 8.18535277)) return false; ts.clear(); // сбросить флаг конца файла ts.seekp(0); // сбросить позицию записи в поток на начало ts.seekg(0); // сбросить позицию чтения из потока на начало ts -40 ' ' -2 ' ' -111 ' ' 42 ' '; ts 2 ' ' -1000 ' ' -4; if (!almost_equal(euclid_norm(ts), 1007.823893)) return false; // Все тесты прошли успешно. return true; >

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *