Почему тип `const char *` в C ++ может хранить Unicode? - PullRequest
2 голосов
/ 17 марта 2020

Я могу написать код, подобный этому:

const char * a = "你好";
cout<<a;

Но когда пишешь так:

char a[] = {'你','好'};
cout<<a;

Он выводит искаженные коды следующим образом:

enter image description here

Я думал, что китайские символы хранятся в wchar_t,

, так как же const char * содержит китайские символы?

Ответы [ 3 ]

5 голосов
/ 17 марта 2020

Когда вы пишете char a[] = {'你','好'};, он объявляет массив символов из 2 элементов (т.е. 2 символа). Так как он не заканчивается нулем, это не строка, которую cout может печатать правильно, и попытка ее печати вызывает неопределенное поведение. Но даже если вы добавите нулевой терминатор { '你', '好', '\0' };, он все равно не будет работать, потому что 1-байтный char не может хранить китайский символ. Фактически, если содержимое между двумя одинарными кавычками длиннее 1 байта (например, 'abcd' или '你' в этом случае), тогда поведение будет определяемым реализацией . См. Литерал с несколькими символами в C и C ++

Однако если вы заключите символы в двойные кавычки "你好", тогда это определенно не 3-байтовый символ с нулевым символом в конце строковый литерал, но последовательность байтов в некоторой кодировке . Стандарт C ++ не определяет, какую кодировку использовать в строковом литерале, но обычно это те байты, которые были сохранены в исходном файле в его кодировке, которая часто является текущей кодовой страницей ANSI в Windows. и UTF-8 в Linux. std::string заключает в себя const char*, так что к нему применяется то же самое

UTF-8 - это кодировка переменной длины , единица измерения которой является байтной, как и другие многобайтовые кодировки , поэтому его базовое представление может быть массивом char[], а "你好" будет строкой из 6 кодовых единиц . Вы можете проверить это с помощью strlen(). OTOH cout ничего не знает об этих символах, а не волнует , если это однобайтовый символ или более. Он просто передает поток байтов в терминал, и задача терминала - отображать их на экране. Но если он хочет, он может определить, как долго символ легко, так же, как это делают терминалы или текстовые редакторы, потому что это определено в кодировке символов


В C ++ есть много других типов символов: wchar_t, char8_t, char16_t и char32_t. Соответствующие им типы строк: std::wstring, std::u8string, std::u16string и std::u32string

Как и char*, кодировка в wchar_t* не определена как по стандарту , но часто UTF-16 в Windows и UTF-32 в Linux. Рекомендуется использовать char8_t, char16_t и char32_t, которые требуют кодирования UTF-8/16/32 независимо от настроек компилятора и кодировки исходного файла

Для преобразования между любыми кодировками вы можете использовать std::codecvt.
Есть также устаревшие преобразователи std::wstring_convert / std::codecvt_utf8 / std::codecvt_utf16 / std::codecvt_utf8_utf16 в старых стандартах C ++ и процедуры преобразования в каждой системе: iconv в Unix и WideCharToMultiByte / MultiByteToWideChar в Windows, но для переносимости лучше использовать современные стандартные функции

Возможно, вы захотите прочитать эти

4 голосов
/ 17 марта 2020

Когда вы пишете строковый литерал в своем коде, используя символы длиной более 1 байта, он преобразуется для вас компилятором. Учтите это:

const char * a = "你好";
cout << strlen(a); // Prints 6

std::cout печатает байты как есть, а символы распознаются терминалом Windows.

С массивом символов подобное преобразование может не выполняться , даже если вы добавите недостающий ноль. Это поведение, определяемое реализацией. Например, в используемом мной компиляторе каждый символ интерпретируется как многосимвольный литерал типа int, а затем усекается до 1-байтового char типа.

4 голосов
/ 17 марта 2020

Здесь есть пара функций кодирования строк. А именно:

1. Кодировка символов

Существует много способов кодирования строк. char не подразумевает 1-байтовые символы. Многобайтовые наборы символов (MBCS) существовали в течение десятилетий до Unicode, и, вероятно, именно так ваш компилятор интерпретирует буквальные китайские символы. Если вы посмотрите в память, которая представляет эту строку, вы почти наверняка увидите, что символы представлены более чем 1 байтом каждый.

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

Мы избегаем этой головной боли в наше время, используя Unicode некоторой формы.

Самый короткий ответ, хотя здесь, это то, что это зависит от того, как ваш компилятор интерпретирует ваш источник. Это определяется реализацией, и обычно есть специфический для компилятора способ указания этого поведения c (для msv c: /utf-8).

Это означает, что ваш второй пример , который предполагает, что символы по 1 байту каждый, может быть успешным, только если ваш компилятор работает с кодировкой, в которой эти символы помещаются в один байт, что, я подозреваю, невозможно. Таким образом, компилятор усекается до 1 символа, и вы получите в основном мусор.

2. Нулевое окончание

Строки, как правило, заканчиваются нулем в C или C ++, то есть после последнего символа значение 0 обозначает конец строки. Строка типа abc представлена ​​в памяти в виде 4 байтов: 'a', 'b', 'c', 0

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

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

...