Как легко управлять строками Unicode в C ++

Я хочу получить каждый символ из строки Unicode. Если этот вопрос плохой, я надеюсь, что вы понимаете.

string str = "öp";
for (int i = 0; i < str.length(); i++) {
 cout << str[i] << endl;
}

В этом случае str [0] - это неправильный символ, потому что длина ö равна 2. Как я могу это сделать? Я очень ценю ваши ответы. Спасибо.

Всего 4 ответа


Чтобы вставить символы (например, новые строки, такие как в примере), между символами строки UTF-8, вы должны делать это только между полными кластерами графем. Прямо сейчас вы добавляете новую строку после неполной кодовой точки, что нарушает кодировку.


Стандарт Unicode здесь . Смотрите этот раздел, в частности:

3.9 Формы кодирования Unicode

UTF-8,

Таблица 3-6. Распределение битов UTF-8

+----------------------------+------------+-------------+------------+-------------+
|        Scalar Value        | First Byte | Second Byte | Third Byte | Fourth Byte |
+----------------------------+------------+-------------+------------+-------------+
| 00000000 0xxxxxxx          | 0xxxxxxx   |             |            |             |
| 00000yyy yyxxxxxx          | 110yyyyy   | 10xxxxxx    |            |             |
| zzzzyyyy yyxxxxxx          | 1110zzzz   | 10yyyyyy    | 10xxxxxx   |             |
| 000uuuuu zzzzyyyy yyxxxxxx | 11110uuu   | 10uuzzzz    | 10yyyyyy   | 10xxxxxx    |
+----------------------------+------------+-------------+------------+-------------+

Исходя из этого, мы можем разработать следующий алгоритм для итерации кодовых точек:

for (int i = 0; i < str.length();) {
    std::cout << str[i];

    if(str[i] & 0x80) {
        std::cout << str[i + 1];
        if(str[i] & 0x20) {
            std::cout << str[i + 2];
            if(str[i] & 0x10) {
                std::cout << str[i + 3];
                i += 4;
            } else {
                i += 3;
            }
        } else {
            i += 2;
        }
    }  else {
        i += 1;
    }

    std::cout << std::endl;
}

Этот тривиальный алгоритм достаточен для вашего примера, если он нормализован в составной форме, т. "ö" - это единая кодовая точка. Однако для общего использования требуется более сложный алгоритм, чтобы различать кластеры графем.

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


Проблема в том, что utf-8 ( не unicode) является многобайтовой кодировкой символов. Наиболее распространенные символы (набор символов ANSI) используют только один байт, но менее распространенные символы (особенно смайлики) могут использовать до 4. Но это далеко не единственная проблема.

Если вы используете только символы из базовой многоязычной плоскости и можете быть уверены, что никогда не столкнетесь с комбинированными , вы можете безопасно использовать std::wstring и wchar_t , потому что wchar_t гарантированно содержит любые символы из BMP.

Но в общем случае Unicode - беспорядок. Даже при использовании char32_t который может содержать любую кодовую точку Юникода, вы не можете быть уверены в наличии биекции между кодовыми точками Юникода и графемами (отображаемыми символами). Например, ЛАТИНСКОЕ МАЛЕНЬКОЕ ПИСЬМО E С ОСТРОМ ( é ) - это символ Unicode U + E9. Но он может быть представлен в разложенном виде как U + 65 U + 0301 или LATIN SMALL LETTER E с последующим ОЧЕРЕДНЫМ АКЦЕНТОМ КОМБИНИРОВАНИЯ. Так что даже при использовании char32_t вы получаете 2 символа для одной графемы, и было бы неправильно разделять их:

wchar32_t eaccute = { 'e', 0x301, 0};

Это действительно представление о. Вы можете скопировать и вставить его, чтобы убедиться, что это не символ U + E9, а разложенный, но в печатном виде не может быть никакой разницы.

TL / DR: за исключением случаев, когда вы уверены, что используете только подмножество кодировки Unicode, которое может быть представлено в гораздо более короткой кодировке как ISO-8859-1 (Latin1), или эквивалент, у вас нет простого способа узнать, как разделить строка из настоящих символов.


Для Windows вы можете установить символьный режим, используя _setmode и wcout для вывода.

#include <iostream>
#include <io.h>
#include <fcntl.h>

using std::wcout;
using std::string;
using std::endl;

int main() {

    if (_setmode(_fileno(stdout), _O_U8TEXT) == -1)
        return 1;

    string str = "öp";

    for (size_t i = 0; i < str.length(); i++)
       wcout << str[i] << endl;

    unsigned char c = str[0]; //<-- for single character
    wcout << (char)c;
}

«Атомная» единица string объекта, очевидно, является другой string (содержащей одну кодовую точку) или char32_t (кодовая точка Unicode). string является наиболее полезной, поскольку ее можно снова составить, и преобразование в UTF не требуется.

Я немного заржавел в C / C ++, но что-то вроде:

string utf8_codepoint(const string& s, int i) {

    // Skip continuation bytes:
    while (s[i] & 0xC0 == 0x80) {
        ++i;
    }

    string cp = s[i];
    if (s[i] & 0xC0 == 0xC0) { // Start byte.
        ++i;
        while (s[i] & 0xC0 == 0x80) { // Continuation bytes.
            cp += s[i];
            ++i;
        }
    }
    return cp;
}

for (size_t i = 0; i < str.length(); i++)
   wcout << utf8_codepoint(str, i) << endl;

for (size_t i = 0; i < str.length(); ) {
   string cp = utf8_codepoint(str, i);
   i += cp.length();
   wcout << cp << endl;
}

Конечно, в Unicode есть акценты нулевой ширины, которые нельзя печатать отдельно, но то же самое относится и к управляющим символам или к отсутствию шрифта с полной поддержкой Unicode (и, следовательно, шрифта размером около 35 МБ).


Есть идеи?

10000