Как легко управлять строками Unicode в C ++ - PullRequest
1 голос
/ 10 февраля 2020

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

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

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

Ответы [ 3 ]

2 голосов
/ 10 февраля 2020

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


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

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

UTF-8

Таблица 3-6. UTF-8 Bit Distribution

+----------------------------+------------+-------------+------------+-------------+
|        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;
}

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

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

1 голос
/ 10 февраля 2020

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

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

Но в общем случае c Unicode - беспорядок. Даже при использовании char32_t, который может содержать любую кодовую точку Юникода, вы не можете быть уверены в наличии биекции между кодовыми точками Юникода и графемами (отображаемыми символами). Например, LATIN SMALL LETTER E WITH ACUTE (é) - это символ 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), или эквивалент, у вас нет простого способа узнать, как разбить строку на истинные символы.

0 голосов
/ 10 февраля 2020

Единица «atomi 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 МБ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...