Как считать символы в строке юникода в C

Допустим, у меня есть строка:

char theString[] = "你们好āa"; 

Учитывая, что моя кодировка является utf-8, эта строка имеет длину 12 байт (три символа hanzi имеют по три байта, латинский символ с макросом – два байта, а «a» – один байт:

 strlen(theString) == 12 

Как я могу подсчитать количество символов? Как я могу сделать эквивалент подписки, чтобы:

 theString[3] == "好" 

Как я могу нарезать, а кот такие строки?

Вы 0xbf только символы, у которых верхние два бита не установлены 0xbf 10 (т. 0xbf Все меньше 0x80 или больше 0xbf ).

Это потому, что все символы с двумя верхними битами, установленными в 10 являются байтами продолжения UTF-8.

См. Здесь описание кодировки и то, как strlen может работать с строкой UTF-8.

Для нарезки и нарезания строк UTF-8 вам в основном нужно следовать тем же правилам. Любой байт, начинающийся с 0 бит или 11 последовательности, является началом кодовой точки UTF-8, все остальные являются символами продолжения.

Лучше всего, если вы не хотите использовать стороннюю библиотеку, просто предоставить функции по следующим направлениям:

 utf8left (char *destbuff, char *srcbuff, size_t sz); utf8mid (char *destbuff, char *srcbuff, size_t pos, size_t sz); utf8rest (char *destbuff, char *srcbuff, size_t pos; 

для получения соответственно:

  • слева sz UTF-8 байтов строки.
  • sz UTF-8 байтов строки, начиная с pos .
  • остальные байты UTF-8 строки, начиная с pos .

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

Самый простой способ – использовать библиотеку, такую ​​как ICU

Попробуйте это для размера:

 #include  #include  #include  #include  #include  // returns the number of utf8 code points in the buffer at s size_t utf8len(char *s) { size_t len = 0; for (; *s; ++s) if ((*s & 0xC0) != 0x80) ++len; return len; } // returns a pointer to the beginning of the pos'th utf8 codepoint // in the buffer at s char *utf8index(char *s, size_t pos) { ++pos; for (; *s; ++s) { if ((*s & 0xC0) != 0x80) --pos; if (pos == 0) return s; } return NULL; } // converts codepoint indexes start and end to byte offsets in the buffer at s void utf8slice(char *s, ssize_t *start, ssize_t *end) { char *p = utf8index(s, *start); *start = p ? p - s : -1; p = utf8index(s, *end); *end = p ? p - s : -1; } // appends the utf8 string at src to dest char *utf8cat(char *dest, char *src) { return strcat(dest, src); } // test program int main(int argc, char **argv) { // slurp all of stdin to p, with length len char *p = malloc(0); size_t len = 0; while (true) { p = realloc(p, len + 0x10000); ssize_t cnt = read(STDIN_FILENO, p + len, 0x10000); if (cnt == -1) { perror("read"); abort(); } else if (cnt == 0) { break; } else { len += cnt; } } // do some demo operations printf("utf8len=%zu\n", utf8len(p)); ssize_t start = 2, end = 3; utf8slice(p, &start, &end); printf("utf8slice[2:3]=%.*s\n", end - start, p + start); start = 3; end = 4; utf8slice(p, &start, &end); printf("utf8slice[3:4]=%.*s\n", end - start, p + start); return 0; } 

Пример прогона:

 matt@stanley:~/Desktop$ echo -n 你们好āa | ./utf8ops utf8len=5 utf8slice[2:3]=好utf8slice[3:4]=ā 

Обратите внимание, что ваш пример имеет одну ошибку. theString[2] == "好"

В зависимости от вашего понятия «характер» этот вопрос может быть более или менее вовлечен.

Прежде всего, вы должны преобразовать свою байтовую строку в строку кодов unicode. Вы можете сделать это с помощью iconv() ICU, но если это единственное, что вы делаете, iconv() намного проще, и это часть POSIX.

Ваша строка кодовых точек юникода может быть чем-то вроде uint32_t[] с нулевым завершением или если у вас есть C1x, массив char32_t . Размер этого массива (т. Е. Его количество, а не его размер в байтах) – это количество кодовых точек (плюс терминатор), и это должно дать вам очень хорошее начало.

Однако понятие «печатаемый символ» довольно сложное, и вы можете предпочесть считать графемы, а не кодовые точки, например, a с акцентом ^ можно выразить как два кодовых пункта unicode или как комбинированный код-код – оба они действительны, и оба они требуются стандартом Юникода для одинаковой обработки. Существует процесс, называемый «нормализацией», который превращает вашу строку в определенную версию, но есть много графем, которые не могут быть выражены как единый код, и вообще нет никакой возможности вокруг соответствующей библиотеки, которая понимает это и считает графемы для вас ,

Тем не менее, вам решать, насколько сложны ваши сценарии и насколько тщательно вы хотите их обработать. Преобразование в кодировки unicode является обязательным, все, что за его пределами, зависит от вашего усмотрения.

Не стесняйтесь задавать вопросы об ICU, если вы решите, что вам это нужно, но не стесняйтесь сначала исследовать гораздо более простой iconv() .

В реальном мире theString[3]=foo; это не значимая операция. Зачем вам когда-либо хотеть заменить персонажа в определенной позиции в строке другим персонажем? Там, конечно, нет задачи обработки текста на естественном языке, для которой эта операция имеет смысл.

Подсчет символов также маловероятен. Сколько персонажей (для вашей идеи «характер») есть в «á»? Как насчет «á»? Теперь как насчет «གི»? Если вам нужна эта информация для реализации какого-либо редактирования текста, вам придется иметь дело с этими трудными вопросами или просто использовать существующий набор инструментов библиотеки / gui. Я бы порекомендовал последнего, если вы не специалист по мировым сценариям и языкам и не подумайте, что можете сделать лучше.

Для всех других целей, strlen сообщает вам именно ту часть информации, которая действительно полезна: сколько занимает пространство памяти. Это то, что необходимо для объединения и разделения строк. Если все, что вы хотите сделать, это комбинировать строки или разделить их на конкретный разделитель, snprintf (или strcat если вы настаиваете …) и strstr – все, что вам нужно.

Если вы хотите выполнять операции на естественном языке с более высоким уровнем, такие как капитализация, разрывы строк и т. Д. Или даже более высокоуровневые операции, такие как плюрализация, длительные изменения и т. Д., Тогда вам понадобится библиотека, такая как ICU или что-то вроде гораздо более высокоуровневые и лингвистически способные (и специфичные для языка (ов), с которым вы работаете).

Опять же, большинство программ не имеют никакой пользы для такого рода вещей и просто нужно собрать и разобрать текст без каких-либо соображений на естественный язык.

 while (s[i]) { if ((s[i] & 0xC0) != 0x80) j++; i++; } return (j); 

Это будет считать символы в строке UTF-8 … (Найдено в этой статье: Еще более быстрый подсчет символов UTF-8 )

Тем не менее, я все еще тупик, нарезая и конкатенируя?!?

В общем случае мы должны использовать другой тип данных для символов Юникода.

Например, вы можете использовать тип данных с широким символом

 wchar_t theString[] = L"你们好āa"; 

Обратите внимание на модификатор L, который говорит, что строка состоит из широких символов.

Длина этой строки может быть рассчитана с использованием функции wcslen , которая ведет себя как strlen .

Одно из того, что неясно из приведенных выше ответов, – это то, почему это не просто. Каждый символ кодируется так или иначе – например, он не должен быть UTF-8, и каждый символ может иметь несколько кодировок с различными способами обработки сочетания акцентов и т. Д. Правила действительно сложны и изменяются путем кодирования (например, utf-8 vs. utf-16).

Этот вопрос имеет огромные проблемы безопасности, поэтому необходимо, чтобы это было сделано правильно. Используйте библиотеку, поставляемую ОС, или известную стороннюю библиотеку для управления строками Unicode; не сворачивайте свои собственные.

Я сделал аналогичные годы реализации. Но у меня нет кода со мной.

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

Я думаю, что это хорошая библиотека UTF8. введите ссылку здесь

Последовательность кодовых точек составляет единый слог / букву / символ во многих других неевропейских языках (например, все языки с индексом)

Итак, когда вы подсчитываете длину ИЛИ находите подстроку (определенно используются случаи поиска подстрок – скажем, играем в игру палача), вам нужно продвигать слог по слогу, а не по кодовой точке по кодовой точке.

Таким образом, определение символа / слога и где вы фактически разбиваете строку на «куски слогов», зависит от характера языка, с которым вы имеете дело. Например, образец слогов на многих языках индексов (хинди, телугу, каннада, малаялам, непальский, тамильский, панджаби и т. Д.) Может быть любым из следующих

 V (Vowel in their primary form appearing at the beginning of the word) C (consonant) C + V (consonant + vowel in their secondary form) C + C + V C + C + C + V 

Вам нужно разобрать строку и найти вышеприведенные шаблоны, чтобы разбить строку и найти подстроки.

Я не думаю, что можно использовать метод общего назначения, который может магически сломать строки вышеописанным способом для любой строки юникода (или последовательности кодовых точек) – поскольку шаблон, который работает для одного языка, может быть неприменим для другого письма ;

Я предполагаю, что могут быть некоторые методы / библиотеки, которые могут принимать некоторые параметры определения / конфигурации в качестве входных данных для разбиения строк юникода на такие слоги слога. Не уверен, хотя! Цените, если кто-то может поделиться тем, как они решили эту проблему, используя любые коммерчески доступные или с открытым исходным кодом методы.