Переважна кількість прикладів кодів з рядками C / C ++ (в будь-яких публікуються джерелах) оперує з нуль-термінальними масивами (ASCIZ) елементів char (в стилі C), або з контейнерним типом string (в стилі C ++), побудованим як надбудова над такими масивами.
Все це чудово працює з рядками латинських (англомовних) символов, але може йти в рознос на рядках, містять символи іншомовних алфавітів (російська, китайський, арабська або іврит). Тут все не так просто ... і дуже погано описано в літературі, що й зрозуміло: англомовних авторів мало турбує питання іншомовної локалізації, а вітчизняні автори, у більшості, переписують і адаптують англомовні публікації, не приділяють уваги цій стороні питання.
Мова C - вельми старий мову програмування, а C ++ успадковує з нього формати і стримується вимогами синтаксичної сумісності з C. Для того, щоб не мати в C / C ++ проблем з такими рядками (званими локалізованими) потрібно розуміти що там з цими локалізаціями відбувається ...
історичний, символи (char) представлялися (1963 рік) стандартом ASCII як молодші 7 біт одного байта, при цьому старший 8-й біт призначався для контролю помилок, що виникли при передачі даних.
Таке кодування дозволяє кодувати максимально всього 128 різних символів, і цього числа ледве вистачає на символи англійського алфавіту (великі і малі), цифрові (код 0x30-0x39), керуючі (код менше 0x20) і спеціальні символи. Коли виникала необхідність представлення національних алфавітів, вводилася альтернативна таблиця символів, наприклад KOI-7 для російської мови.
Перемикання в потоці вводу-виводу на альтернативну таблицю символів вироблялося символом з кодом 0x18 (код називається: управління пристроєм 2) в потоці, а повернення до основну таблицю ASCII - символом з кодом 0x17 (управління пристроєм 1).
пізніше, з середини 80-х років, з часу широкого поширення IBM PC і заміни ними комп'ютерів інших сімейств, стандарт ASCII було розширено на 8-й біт байта char, байт міг представляти 256 символов: молодші 127 представляли вихідну таблицю ASCII (з латинським шрифтом), а старші - національний алфавіт.
Але, оскільки національні алфавіти можуть бути найрізноманітнішими, то для підтримки кожного з них треба було ввести свою кодову сторінку, например, для російської мови це можуть бути сторінки CP-866 (MS в), CP-1251 (в ОС Windows), Ні-8г (в UNIX, Linux) - і кожна з цих сторінок пропонує свій, відрізняється від інших, порядок символів російської мови. При этом, для коректного відображення (або декодування) будь локалізованої символьного рядка потрібно обов'язково знати кодову сторінку в якій вона представлена.
Для того, щоб покласти край цьому вавілонського стовпотворіння мовних кодових сторінок, був запропонований (1991м) стандарт уявлення UNICODE, під час передачі сигналу якого кожен символ кодується 32-біт значенням (4 байта, але не всі 32-біт значення припустимі). Застосування даного стандарту дозволяє закодувати величезну кількість символів різних систем писемності.
документи, закодовані за стандартом UNICODE, можуть містити в єдиному тексті японські та китайські ієрогліфи, букви латиниці, кирилиці, грецького алфавіту (α, е, Я, Fr., р, L, F, Про ...), математичні символи, символи музичної нотної нотації, символи вимерлих, рідкісних, екзотичних народностей. При цьому немає необхідності в перемиканні кодових сторінок. Наприклад, ось як виглядають деякі символи мови, позначеного як “singaliskii”:
1 | ඣ, ඤ, ඥ |
Перший UNICODE стандарт був випущений в 91-му році. Останній на даний момент - в 2017 і він описує 136755 різноманітних символів.
Але UNICODE - це ще тільки стандарт уявлення кожного символу. Для подання цього символу в конкретній операційній системі (або мовою програмування) потрібна ще система кодування символів UNICODE.
- Досить широко використовуються системи кодування:
UTF-8 - для подання кожного символу використовуються 4 байта, безпосереднє чисельне значення коду UNICODE - UTF-16 - для подання найбільш часто використовуваних символів використовуються 2 байта (перші 65536 позицій), а інші видаються у вигляді у вигляді «сурогатних пар». Таке кодування використовується в операційних системах Windows починаючи з Windows NT.
- UTF-32 - для подання кожного символу використовуються сукупність електронних даних змінної довжини: от 1 байта для символів основної таблиці ASCII, до 6 байт для рідко використовуваних символів (символи російського алфавіту кодуються 2-мя байтами). Це кодування створювалася пізніше інших для операційних систем Plan 9 і Inferno в 1992р. Кеном Томпсоном и Робертом Пайком с коллегами, і увійшла як єдина і основна кодування символьних рядків в більш пізніх мовах програмування Python і Go. Таке кодування використовується, на сьогодні повсюдно, в POSIX / UNIX операційних системах, Linux.
Повертаючись до того, що C / C ++ старе сімейство мов програмування, для подання в них локалізованих символів потрібно ввести новий тип даних - широкі символи wchar_t замість char (тип даних з'явився в стандарті C89, але, в повній мірі з API підтримки, тільки в стандарті C99). Замість малих функцій бібліотеки C виду str *() для широких пропонуються їх повні аналоги, але у вигляді wcs *() (замість префікса str записуємо префікс wcs). У різних системах wchar_t може мати різну розрядність (в Linux це int32_t, в Windows, int16_t) але для програміста це не має значення і не створює відмінностей.
1 2 3 4 5 6 | #include <stdio.h> #include <wchar.h> int main( void ) { printf( "размер символа wchar_t вашей реализации = %d байт\n", (int)sizeof( wchar_t ) ); } |
1 2 | $ ./0 размер символа wchar_t вашей реализации = 4 байт |
Для роботи та перетворення мультибайтних послідовностей записаних в кодуванні UTF-8 в C / C ++ вводиться сімейство функцій виду mb *(): mbtowc(), mblen(), mbstowcs(), wcstombs() та ін. Це механізм взаємних перетворень між масивами char[] (в яких також виражаються рядки UTF-8) і wchar_t[]. Якщо ви не стикаєтеся з кодуванням UTF-8 (що з великою ймовірністю має місце в Windows), то ця група функцій вас не повинна займати.
аналогічно, замість контейнерного класу C ++ string вводиться аналогічний контейнерний клас широких символів wstring.
Саме про техніку роботи з широкими локалізованими рядками ми поговоримо в наступній статті. А поки 1-й елементарний приклад ... без коментарів - як привід для роздумів (зверніть увагу і поясніть, що виклик strlen() в кожному випадку дає число байт в рядку явно не відповідає візуально видиме число букв в ній):
1 2 3 4 5 6 | #include <stdio.h> #include <string.h> int main() { char str[] = "Привет, 世界"; printf( "%s [%d байт]\n", str, (int)strlen( str ) ); } |
1 2 | $ ./1 Привет, 世界 [20 байт] |
P.S. З великою детальністю про локалізацію в C / C ++ і роботі з локалізованими рядками, хто цікавиться з більшою докладністю, можуть почитати тут: Мовна локалізація C / C ++ Мовна локалізація C / C ++ - там пояснень більш 22 сторінок формату офісного документа.
Деякі особливості роботи з російськомовними рядками, що не описуються зазвичай при розгляді рядків char, будуть розглянуті в наступних статтях в продовження цієї теми.
Слідкуйте…
Я так розумію в текст вкралася помилка, і мова йде про послідовності UTF-8, 16 і 32
І я так теж розумію! ОК!
хлопці, спасибо! Виправлено
Автор статті як я розумію переплутав опис UTF-8 і UTF-32.