Для итерации по словам строки в кодировке UTF8 мне понадобилось разобраться в том, как узнать по-первому байту символа общее количество его байт. Я только начинаю разбираться с UTF8, поэтому не совсем понимаю наверняка механику битовых операций, которая для этого используется (судя по большинству примеров в интернете).
На StackOverflow я нашёл два решения:
1) Способ 1
size_t utf8_char_length(char firstbyte) {
if ((firstbyte & 0xC0) == 0xC0) {
if ((firstbyte & 0xF0) == 0xF0) {
return 4;
} else if ((firstbyte & 0xE0) == 0xE0) {
return 3;
} else {
return 2;
}
} else {
return 1;
}
}
2) Второе решение найдено здесь, оно использует другой способ: вместо битовой операции & используется "lookup" по следующему массиву из 256 символов:
static const size_t utf8_skip_data[256] = {
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
};
К сожалению, я сейчас не имею возможности досконально разобраться с UTF8, поэтому просто ищу надёжную функцию, которая будет давать мне правильные результаты для символов в кодировке в UTF8 (точнее для первых байт этих символов). Мне больше нравится второе решение, но я хочу услышать мнение более опытных специалистов:
какую функцию стоит использовать для такой задачи: 1, 2 или, может быть, вы знаете какую-то проверенную свою функцию, которая лучше, чем эти две?
P.S. Кстати, если кто-то может объяснить, откуда взялась lookup-таблица, буду признателен. На SO пишут, что она взята из исходного кода glib's gutf8.c. Так вот интересно, какой принцип лежит в её основе.
Спасибо.
Ответ
Вкратце алгоритм кодирования символов в UTF8 выглядит так:
Если код символа меньше 128, то кодируется как есть.
Если больше или равно, то кодируется в следующем формате: в первом байте в единичном коде записывается количество использованных байт включая первый (то есть записывается такое число единиц, сколько байт в числе; теоретически формат UTF8 не накладывает ограничений на количество байт, используемых для записи числа, но обычно используется не более 6). Затем записывается 0 (чтобы отделить количество байт числа от значащих разрядов числа), после чего идут биты, представляющие число. Каждый следующий байт начинается с последовательности 10 (чтобы не путать однобайтные и многобайтные символы - все однобайтные начинаются с нуля).
Например,
111 0 0011 10 011101 10 000100
Сначала 3 единицы, значит, для записи используется 3 байта.
Затем 0, отделяющий запись числа байт от записи числа
Дальше значащие разряды числа. Первые 2 бита каждого байта (10) к ним не относятся - это метки, означающие, что это второй (третий, и т. д.) байт записи.
Таким образом, записанный код - 0011011101000100. Это к вопросу о том, откуда взялась lookup-таблица. К вопросу какую функцию использовать - исходите из потребностей. Если вы всегда будете работать с символами, код которых не превышает 256, то используйте lookup-таблицу, она быстрее. Если же планируется полная поддержка Юникода, то используйте функцию. Только её придётся модифицировать, так как она не распознает 5- и 6-байтные символы.
UPD
Прошу прощения, выражение 'lookup-таблица' сбило меня с толку и навеяло неверные ассоциации. Да, действительно, эта таблица предназначена для проверки первого байта последовательности и решения о длине кода. Лично я всё равно склоняюсь к функции - она выглядит гораздо лаконичнее. Но lookup-таблица работает быстрее, если это принципиально, лучше использовать её.