Как использовать юникод в c
Перейти к содержимому

Как использовать юникод в c

Как использовать юникод в c

Программируя на С, каждый неанглоязычный программист рано или поздно сталкивается с неожиданной и досадной трудностью вывода русского текста в наборе символов Юникод (Unicode) и в кодировке UTF8. Чтение стандарта С99 и описания библиотеки libc навевает уныние, поиск в Интернете выдает массу полезных ссылок об использовании wchar_t, не приводя в то же время простых примеров работы с «wchar.h» и широкими функциями.

Эта заметка рассматривает два самых простых способа вывода русского текста из консольных С-программ.

Прежде чем мы продолжим, я хотел бы привести строки из Евангелия:

Лично для вас благая весть — Единородный Сын Божий Иисус Христос любит вас, Он взошел на крест за ваши грехи, был распят и на третий день воскрес, сел одесную Бога и открыл нам дорогу в Царствие Небесное.

Сейчас, в это время года перед Пасхой, Православные христиане приносят Господу жертву поста и молитвы. Мы делаем это в воспоминание великого подвига Иисуса Христа, совершенного для нас и за нас. Мы вспоминаем со стыдом и раскаянием свои грехи, исповедуем их в молитве перед Богом и просим Его милости к нам, христианам. Каждый из нас молится за своих родных, друзей, соседей, коллег и всех людей, окружающих нас. Эта молитва обретает новую силу во время поста.

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

Покайтесь, примите Иисуса как вашего Спасителя, ибо наступают последние времена и время близко — стоит Судья у ворот.

Пожалуйста, в своих каждодневных трудах, какими бы занятыми вы себе ни казались — находите время для Бога, Его заповедей и Библии.

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

Вернемся к нашим техническим деталям.

Я предполагаю что уважаемый читатель владеет необходимыми знаниями и навыками использования С-компиляторов и знаком с языком, что позволит мне изложить самый простой подход к работе с русскими Юникод-символами в кодировке UTF8.

Простейший способ вывода

Предположим, что мы хотим всего лишь вывести некоторый текст из нашей программы, без необходимости его обработки. Сделать это можно очень просто, используя стандартные одно-байтные функции. Убедитесь что консоль системы установлена в UTF8 и текст вашей программы использует эту же кодировку.

Как видно, текст выведен правильно, несмотря на использование однобайтовых ASCII функций. Что же произошло? Фактически, мы «закодировали» нашу константу «Добро Пожаловать» не в 16 байт, а в 32 байта, и вывели их последовательно, один за другим. На экране, поток ввода-вывода stdout всего лишь преобразовал эти 32 байта в необходимые 15 двухбайтных Unicode символов (и 2 однобайтных символа), в соответствии с Unicode UTF8 encoding. Проверим это:

Заметьте выделенные жирным последовательности битов «110» и «10» в соседних байтах — это и есть кодировка UTF8. Именно так она распознается системой. Сам же Unicode код символа содержится в оставшихся битах двух соседних байтов. Также заметьте код пробела в середине и конец строки в конце — они по-прежнему однобайтные, поскольку все ASCII символы представлены в их оригинальном виде в UTF8 кодировке.

Таким образом, рассматриваемый простейший подход не является правильным решением — это всего лишь небольшая «хитрость», основанная на знании работы системы (и правильности реализации стандартной библиотеки С и эмулятора терминала).

Стандартный подход к выводу русских Unicode символов

Способ, описанный в предыдущем разделе, имеет огромный недостаток — подобный подход не позволит нам использовать поиск, преобразование символов строк и прочие функции стандартной библиотеки С. На мой взгляд, следующий подход является правильным и использует средства из стандарта ANSI C (доступные и в С99).

Строго говоря, все что мы должны были осмыслить это строку из «ISO/IEC 9899:1999», раздел «7.11.1.1 The setlocale function». Стандарт говорит: «At program startup, the equivalent of setlocale(LC_ALL, «C»); is executed». Вот она, причина вопросительных знаков или прочих «крокозяблей» на выводе!

Функции широкого вывода (например, putwchar) получают Unicode символ типа wchar_t во «внутренней» кодировке (4 байта UCS для Линукс, смотрите описание «libc»), перекодируют этот символ для «внешнего» представления в соответствии с текущей локалью («C» по умолчанию) и выводят в поток stdout, который в нашей системе настроен на «en_EN.utf8». Вот и несоответствие — символ для однобайтной локали «С» выводится в многобайтную локаль UTF8 (и воспринимается ею как пол-символа!).

Решение очевидно — надо всего лишь сказать программе о текущей настройке локали. После этого, мы обязаны использовать только широкие версии функций и широкие типы данных wchar_t и wint_t. Поначалу это раздражает, но незначительное неудобство с лихвой компенсируется возможностью полноценной работы с широкими символами, как показано в примере — буква ‘ё’ правильно сменила регистр в ‘Ё’, заглавные буквы верно распознаются стандартными библиотечными функциями.

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

c++ и unicode

Начал переходить на API. Не могли бы вы объяснить, для чего нужны TEXT и _T и что такое unicode?

Unicode это единая кодовая таблица с символами для всех существующих языков + математические символы, акценты над буквами (приписываются после букв и рисуются поверх них) и так далее. Есть разные способы представления символов в юникоде (UTF8, UTF16, он же UCS2, UTF32, ASCII неюникод, и так далее), но все они после раскодирования становятся обычным 32-битным юникодом, где одному символу соответствует 1 знак юникода. Чтобы поглядеть что там есть, запусти в винде Win+R, набери "charmap" или выбери в дополнительных программах таблицу символов, и выбери какой-нибудь юникод-шрифт, Arial например.

В С++ нет поддержки юникода в самом языке, но есть зародыш поддержки в стандартной библиотеке в виде функций работы с wchar_t (16-битный UCS-2): функции с префиксом wcs* и utf-8: функции с префиксом mbs*
wcscpy mbscpy
wcscmp mbscmp
и так далее.
Макрос TEXT() указывает компилятору, что строка может содержать юникод. http://msdn.microsoft.com/en-us/library/windows/desktop/dd374074%… vs.85%29.aspx
Макрос _T() означает что тебе по барабану что будет со строкой, она не содержит юникода (как-то так я это понимаю, сам практически не пользовался). В некоторых режимах компиляции это требует прибавления L"" перед строкой, макрос это делает http://social.msdn.microsoft.com/Forums/en/vcgeneral/thread/8ce6d… -54af91766e9f

kvakvs
> wchar_t (16-битный UCS-2)
В нормальных системах wchar_t 32-битный, а кодировки UCS-2 уже не существует.

Для кроссплатформенной работы с юникодом советую принцип UTF8 everywhere — т. е. везде работать с char*/std::string, предполагая кодировку UTF8, и только для вызова функций, которые эту кодировку не понимают (WinAPI) преобразовывать куда надо. Соответственно никаких кривых макросов, wchar_t и L"" использовать не надо. Хотя, конечно, работать с юникодом под Windows — это тот еще гемор (с помощью стандартной библиотеки C/C++ в принципе невозможно открыть файл с юникодом в названии!).

klavdraiver
если есть С++11, то в стандарте многое уже есть из коробки для utf (-8, -16, -32, хотя и тут комитет не обошёлся без двусмысленных конструкций типа codecvt_utf16<char32_t>)

даже в с++03 можно без большого труда работать с конкретно utf-8 (всего-то найти нормальный codecvt для него и использовать его как narrow строки), wide же там работает только как платформенный или ucs-2, или ucs-4 (что, обычно, хватало для бытовых нужд на конкретной платформе)

также boost.locale серьезно упрощает жизнь с кодировками под с++03

>UTF16, он же UCS2
Это же разные вещи. В UCS2 все символы имеют постоянную длину и может быть закодирована только часть Юникода, а UTF16 — кодировка с переменной длиной.

Есть ли какой-нибудь простой способ работать с Юникодом? Например, возможно ли сделать ToLower или ToUpper, не храня всю таблицу Юникода в программе? А то у меня велосипедный класс строк, я решил перейти на UTF8.

gammaker
> UTF16 — кодировка с переменной длиной.
16 бит, фиксировано. UTF-8 с переменной.

gammaker
> возможно ли сделать ToLower или ToUpper, не храня всю таблицу Юникода в программе
не говоря уж о том, что всё написано до нас (с) — http://www.boost.org/doc/libs/1_53_0/libs/locale/doc/html/conversions.html

в отсутствии нормальной локали utf-8 средствами с++03 это должно выглядеть как:
1) utf-8 -> wchar_t (делается, например, при помощи найденного где-нибудь utf8_codecvt)
2) std::toupper(wchar_t, locale)
3) wchar_t -> utf-8 (опять же с помощью того же utf8_codecvt)

> Начал переходить на API.
Дальше не читал, что бы посмеяться — этого достаточно xD

Интересно, это клон вендера-разора или просто такой же упорок?

slava_mib
> > Начал переходить на API.
> Дальше не читал, что бы посмеяться — этого достаточно xD
Из контекста же всем понятно что он имел в виду. Ну видно, что недостаток словарного запаса )

>16 бит, фиксировано.
Википедию почитай:

UTF-16 (англ. Unicode Transformation Format) в информатике — один из способов кодирования символов из Unicode в виде последовательности 16-битных слов. Данная кодировка позволяет записывать символы Юникода в диапазонах U+0000..U+D7FF и U+E000..U+10FFFF (всего 1 112 064 штук). При этом каждый символ записывается одним или двумя словами (суррогатная пара).

Иначе зачем тогда второе название UCS-2?

>не говоря уж о том, что всё написано до нас
Меня не интересуют отдельные библиотеки. Меня интересует сам алгоритм и, желательно, его реализация на C\C++ без лишних зависимостей: без STL, без Boost, без Windows API. Только вручную, возможно с использованием стандартной библиотеки C.

Для пунктов 1 и 3 я уже нашёл файлик с реализацией на языке C.

>2) std::toupper(wchar_t, locale)
STL мне не подойдёт по религиозным причинам. К тому же зачем ему указывать локаль? Разве одного кода символа недостаточно?

>> Начал переходить на API.
>Дальше не читал, что бы посмеяться — этого достаточно xD
Я когда начинал учиться программировать, тоже WinAPI просто API называл. Ну и что теперь, над всеми новичками смеяться?

Кстати, как сравнивать UTF-8 строки? У них вроде один и тот же символ может быть представлен по-разному.

> Из контекста же всем понятно что он имел в виду.
kvakvs, мда?
Мне кажется, понятно только что:
Iskander
> это клон вендера-разора или просто такой же упорок?

З.Ы. Посмотрел созданные им темы — стало понятно "из контекста". А ещё стало понятно, что Iskander полностью прав: больше 30 тем за 3 месяца и все из серии "тупой, ещё тупее" — чувак на 100% клон ведроида-разора, и темы его настолько же тупы, и сам он настолько же ленив, и искать ответы самому ему настолько же лень.

Вот выжимка:
>Начал переходить на API. Не могли бы вы объяснить, для чего нужны TEXT и _T и что такое unicode?

>Дайте пожалуйста пример сокетов, передающих данные по Интернету, а не по сети, желательно в виде чата.

>1>D:\progs\c++\c++2010\watry\Debug\watry.exe : fatal error LNK1120: 1 неразрешенных внешних элементов
>Подскажите пожалуйста, в чём проблема?

>У меня есть звук взрыва.
>Нужно вставить его в программу, но так, чтобы он кончался только после того как закончит воспроизводиться.

>попытался запустить animate.
>Анимация не работает.
>дайте пожалуйста ссылку.

>Только начал читать по opengl.
>Непонятна суть команды gltranslatef.
>Подскажите пожалуйста, а то в учебнике всё на ней базируется.

>Я взял проект eх31 из 3 главы книги "Краснов. OpenGL графика в проектах Delphi". На 353 строке появляется ошибка о длинне строк, хотя она всего 18 символов.
>В чём проблема?

gammaker
> Кстати, как сравнивать UTF-8 строки? У них вроде один и тот же символ может быть представлен по-разному.
Обычно валидным считается только минимальное представление, см. тут (п. 4).

> UTF16, он же UCS2

> UTF16 — кодировка с переменной длиной.

> 16 бит, фиксировано. UTF-8 с переменной.

UCS-n — фиксированная длина кода символа (n — размер кода в байтах)
UTF-n — переменная длина кода символа (n — размер элемента кода в битах)

Как использовать символы Юникода в командной строке Windows?

У нас есть проект в Team Foundation Server (TFS), в котором есть не английский символ (š). При попытке написать несколько вещей, связанных со сборкой, мы столкнулись с проблемой — мы не можем передать букву š инструментам командной строки. Командная строка или что-то еще портит ее, и утилита tf.exe не может найти указанный проект.

Я пробовал разные форматы для .bat-файла (ANSI, UTF-8 с и без спецификации ), а также создавал сценарии в JavaScript (что по сути является Unicode) — но не повезло. Как мне выполнить программу и передать ей командную строку Unicode ?

Мой опыт: я использую ввод / вывод Unicode в консоли в течение многих лет (и делаю это много раз в день. Более того, я разрабатываю инструменты поддержки именно для этой задачи). Существует очень мало проблем, насколько вы понимаете следующие факты / ограничения:

  • CMD и «консоль» являются несвязанными факторами. CMD.exe это просто одна из программ, которые готовы «работать внутри» консоли («консольные приложения»).
  • AFAIK, CMD имеет отличную поддержку Unicode; Вы можете вводить / выводить все символы Unicode, когда активна любая кодовая страница.
  • Консоль Windows имеет МНОГО поддержки Unicode — но она не идеальна (просто «достаточно хороша»; см. Ниже).
  • chcp 65001 это очень опасно. Если программа не была специально разработана для обхода дефектов в API Windows (или не использует библиотеку времени выполнения C, которая имеет эти обходные пути), она не будет работать надежно. Win8 исправляет половину этих проблем cp65001 , но остальное все еще применимо к Win10 .
  • Я работаю в cp1252 . Как я уже сказал: для ввода / вывода Unicode в консоли не нужно устанавливать кодовую страницу .

Детали

  • Для чтения / записи Unicode на консоль приложение (или его библиотека времени выполнения C) должно быть достаточно умным, чтобы использовать не File-I/O API, а Console-I/O API. (Например, посмотрите, как это делает Python .)
  • Аналогично, чтобы читать аргументы командной строки Unicode, приложение (или его библиотека времени выполнения C) должно быть достаточно умным, чтобы использовать соответствующий API.
  • Консольный рендеринг шрифтов поддерживает только символы Юникода в BMP (другими словами: ниже U+10000 ). Поддерживается только простая отрисовка текста (поэтому европейские и некоторые восточноазиатские языки должны нормально работать, если используются предварительно составленные формы). [Здесь есть мелкий мелкий шрифт для восточной азии и для символов U + 0000, U + 0001, U + 30FB.]

Практические соображения

Значения по умолчанию для Window не очень полезны. Для лучшего опыта нужно настроить 3 части конфигурации:

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

Еще одна ошибка с «Вставкой» в консольное приложение (очень техническое):

  • Ввод шестнадцатеричных символов обеспечивает ввод символа KeyUp of Alt ; все остальные способы доставки персонажа происходят KeyDown ; так много приложений не готовы видеть персонажа на KeyUp . (Применимо только к приложениям, использующим Console-I/O API.)
  • Вывод: многие приложения не будут реагировать на входные события HEX.
  • Кроме того, то, что происходит с «вставленным» символом, зависит от текущей раскладки клавиатуры: если символ можно набирать без использования префиксных клавиш (но с произвольной сложной комбинацией модификаторов, как в Ctrl-Alt-AltGr-Kana-Shift-Gray* ), то он доставляется с помощью эмулируемого нажатия клавиши. Это то, что ожидает любое приложение — так что вставка всего, что содержит только такие символы, это хорошо.
  • Однако «другие» символы доставляются путем эмуляции ввода HEX .

Вывод : если ваша раскладка клавиатура поддерживает ввод много символов без ключей приставки, некоторые приложения багги может пропустить символыкогда вы Paste через интерфейс Консоли: Alt-Space E P . ( Вот почему я рекомендую использовать раскладки клавиатуры!)

Следует также помнить, что «альтернативные,« более функциональные »консоли» для Windows вовсе не являются консолями . Они не поддерживают Console-I/O API, поэтому программы, которые используют эти API, не будут работать. (Программы, которые используют только «API-интерфейсы файлового ввода-вывода для файловых дескрипторов консоли», будут работать нормально.)

Одним из примеров таких не консольных является частью MicroSoft Powershell . Я не использую это; чтобы экспериментировать, нажмите и отпустите WinKey , затем введите powershell .

(С другой стороны, существуют программы, такие как ConEmu или ANSICON которые пытаются сделать больше: они «пытаются» перехватить Console-I/O API, чтобы заставить «настоящие консольные приложения» работать тоже. Это определенно работает для игрушечных примеров программ; в реальной жизни это может или может не решить ваши конкретные проблемы. Эксперимент.)

Резюме

установить шрифт, раскладку клавиатуры (и при желании разрешить ввод в шестнадцатеричном формате).

используйте только программы, которые проходят через Console-I/O API и принимают аргументы командной строки Unicode. Например, любая cygwin скомпилированная программа должна быть в порядке. Как я уже сказал, CMD тоже хорошо.

UPD: Изначально, из-за ошибки cp65001 , я смешивал слои Kernel и CRTL ( UPD²: и API пользовательского режима Windows!). Также: Win8 исправляет одну половину этой ошибки; Я разъяснил раздел о «лучшей консольной» программе и добавил ссылку на то, как это делает Python.

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

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