Страницы

Поиск по вопросам

пятница, 12 июля 2019 г.

Вопрос про работу с кириллицей (UTF-8) в C++ | Linux

Вводная
Допустим есть файл сохранённый в UTF8. Читать я его могу исключительно std::ifstream (без std::wifstream).
После прочтения файла, я хочу иметь возможность итерироваться по utf-8 символам, и даже сравнивать их
for (size_t i = 0; i < utf8String.size(); i++) { if (utf8String[i] == 'ф') { //... } }
Как мне этого добиться? Вероятно для этого потребуется сконструировать строку, std::wstring, в linux она конструируется из wchar_t, которые занимают 4 байта.
Тогда вопрос в следующем: как сделать конструирование из std::string - которая хранит какой-то utf8 текст - строки std::wstring
Самый первый вариант который приходит в голову: wstring wstr(str.begin(), str.end()); К сожалению не подойдет.
Чтобы понять почему не подойдёт потребуется класс, который показывает битовое содержимое вот он (pastebin)
Если посмотреть на utf8 строку "добро", хранимую в string то например 'д' будет представлено так 11010000 10110100 всё верно.
А вот символ 'д' из наивно сконструированной строки занимает целых 2 элемента wchar_t 11010000 11111111 11111111 11111111 10110100 11111111 11111111 11111111 и вообще не похож на utf-8.
Я могу объяснить почему он стал таким, но не будем (вкратце это от того что wchar_t=char а char<0 для любых широких символов + порядок байт на машине)
Я нашел что string -> wstring можно сделать linux.org.ru Но в моём gcc нет такого хедера как #include
Решил сделать сам конструирование из string в wstring как-то так (pastebin)
Теперь вопрос ко знатокам, я не разу не работал плотно с локалью, поэтому к своему стыду не знаю всех тонкостей, воспринимаю работу с локалью так как описано здесь https://ru.cppreference.com/w/cpp/locale/locale
т.е. просто нужно вставить в код
locale::global(std::locale("") ); wcout.imbue(std::locale());
Понимаю что это даст не более чем какие-то "национальные преобразования" например даты, денежных форматов, дробных форматов. Но именно слова закодированные в utf8 никак не должны затрагиваться манипуляциями с локалями. Верно? Ведь для этого и существует utf8, чтобы однозначно представлять символы разных стран кодом. Код буквы 'Д' внезависимости от любых манипуляций с локакалью (или отсутствием этих манипуляций) есть 1101000010110100 Правильно?
Вроде бы да, но почему-то ручное преобразовние не работает. После работы этой ф-ии букве 'д' будет соответствовать код 11010000 10110100 00000000 00000000 Что вроде бы верно. Но если так сконструированную строку передать на wcout то оно выведет крокозябры и иероглифы.
Сайт https://sites.google.com/site/nathanlexwww/tools/utf8-convert тоже говорит что кодировка буквы 'д' верна.
Тогда нужно посмотреть что из себя представляет std::wstring wstr = L"добро"; Как выяснилось её внутреннее представление вообще не является utf8 (хотя сам исходник сохранен именно в этой кодировке)
символу 'д' там соответствует 00110100 00000100 00000000 00000000
А теперь ряд вопросов 1. Правильно ли я понимаю что никакие манипуляции со стандартными наборами локалей как такие:
locale::global(std::locale("") ); wcout.imbue(std::locale());
Не должны влиять на байтовое/битовое представление велечин которые будут присвоены таким образом
std::wstring = L"добро"
?
Debian 8, gcc - все стандартное, стандартная локаль ru_RU.UTF-8 в системе и консоли, исходный код сохранен в UTF-8. В какой кодировке в памяти программы будет сохранена строка std::wstring = L"добро" ? Есть ли возможность сделать так чтобы std::wstring = L"добро" сохранялась именно в utf8 а не в непонятный формат?
Мне почему-то сейчас думается что в g++ должна быть какая-то опция возможно для чтобы он работал с utf-8, а по дефолту он L"добро" во что-то другое преобразует.
правильно ли я конструирую wchar_t символы для x86-64? Есть ли какие-то легковесные библиотеки, которые, вкомпиляясь статически в проект не сильно его утяжелят, обеспечивают высокоуровневый и простой синтаксис наподобии такого же std::wstring = makeUTF8Str(const std::string&) - и без всяких непонятных манипуляций с актуализацией шаблонов wstring_convert и кучей других приседаний?
весь код по которому я пытаюсь понять utf-8 здесь
Ребят, если желаете помочь ответом то давайте по существу, а не просто ради флейма как сейчас есть один из ответов ниже - который не отвечает ни на однин из поставленных вопросов, но хочет узнать о "выходе за границы стринга" :)


Ответ

Итак, резюмируя ответы на вопросы:
Да, правильно.
Utf-32. Не смотря на то что в моём линуксе дефолтом кодировка utf8 в системе, и исходник сохранён в ней, сам копилятор когда читает такое L”строка” в озу данную строку занесёт к кодировке utf-32. С этим помог разобраться сайт, который показывает все варианты кодировок для конкретного символа https://www.fileformat.info/info/unicode/char/0424/index.htm Такой возможности не нашел, т.е. std::wstring = L"добро" на прямую такого нет чтобы в какой-то свой формат, но т.к. это си++ можно переопределить оператор = - но это лишняя трата времени причем бесполезно, т.к. неявные преобразования не есть хорошо, особенно для тех кто потом будет сопровождать код Правильно, но не нужно, ибо конкретная система работает с utf32 Есть, большое спасибо пользователю ixSci за его ответ, да и за его блог (который я иногда находил через поисковик и читал)
Лично я выбрал эту библиотеку http://utfcpp.sourceforge.net/, она очень проста (как по реализации и размеру, чтобы подключить к проекту в любой ос нужно всего один хедер, а сама библиотека всего в 4 файлах кода) в добавок кросплатформенна.
С этой библиотекой utf8 можно преобразовать так сказать в нативный для бинарника создаваемого моим компилятором вид очень просто:
wstring toUtf8(const string &str) { std::wstring ret; utf8::utf8to32(str.begin(), str.end(), back_inserter(ret)); return ret; }
и иметь возможность корректно перемещаться посимвольно по строке, и сравнивать как буквы так и строки if(mystr == L"g++ широкая строка")

Комментариев нет:

Отправить комментарий