Как подсчитать символы в строке юникода в C
допустим у меня есть строка:
char theString[] = "你们好āa";
учитывая, что моя кодировка utf-8, эта строка имеет длину 12 байт (три символа hanzi-три байта каждый, латинский символ с Макроном-два байта, а " a " - один байт:
strlen(theString) == 12
как я могу подсчитать количество символов? Как я могу сделать эквивалент подписки, чтобы:
theString[3] == "好"
как я могу нарезать, и кошка такие строки?
10 ответов:
вы только подсчитываете символы, которые имеют два верхних бита не установлены в
10(т. е. все, что меньше0x80или более0xbf).это потому, что все символы с двумя верхними битами установлены в
10являются байтами продолжения UTF-8.посмотреть здесь для описания кодировки и как
strlenможет работать на строке UTF-8.для нарезки и нарезки UTF-8 строк, вы в основном должны следовать тому же правила. Любой байт, начинающийся с
0бит или a11последовательность-это начало кодовой точки 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;чтобы получить, соответственно:
- левой
szUTF-8 байт строки.- the
szUTF-8 байт строки, начиная сpos.- остальная часть UTF-8 байт строки, начиная с
pos.это будет достойный строительный блок, чтобы иметь возможность манипулировать строками достаточно для ваших целей.
самый простой способ-использовать библиотеку как ICU
попробуйте это для размера:
#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> // 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()из реанимации, хотя если это единственное, что вы делаете,iconv()намного проще, и это часть POSIX.ваша строка кодовых точек unicode может быть чем-то вроде null-terminated
uint32_t[], или если у вас есть C1x, массивchar32_t. Размер этого массива (т. е. его количество элементов, а не его размер в байтах) - это количество кодовых точек (плюс Терминатор), и это должно дать вам очень хорошее начало.однако понятие "печатный символ" довольно сложное, и вы можете предпочесть считать графемы вместо кодовых точек - например,
aС акцентом^может быть выражено в виде двух кодовых точек Юникода или в виде комбинированной устаревшей кодовой точкиâ- оба действительны, и оба необходимы стандарт 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 подсчет символов)
однако я все еще озадачен нарезкой и конкатенацией?!?
В общем, мы должны использовать другой тип данных для символов Unicode.
например, можно использовать тип данных wide char
wchar_t theString[] = L"你们好āa";обратите внимание на модификатор L, который говорит, что строка состоит из широких символов.
длина этой строки может быть вычислена с помощью
wcslenфункция, которая ведет себя какstrlen.
одна вещь, которая не ясна из приведенных выше ответов, - это то, почему это не просто. Каждый символ кодируется тем или иным способом - например, он не должен быть UTF-8 - и каждый символ может иметь несколько кодировок, с различными способами обработки комбинирования акцентов и т. д. Правила действительно сложны и зависят от кодировки (например, utf-8 против 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вам нужно разобрать строку и искать вышеуказанные шаблоны, чтобы разбить строку и найти подстроки.
Я не думаю, что можно иметь метод общего назначения, который может волшебным образом разбейте строки указанным выше способом для любой строки unicode (или последовательности кодовых точек) - поскольку шаблон, который работает для одного языка, может быть неприменим для другой буквы;
Я думаю, что могут быть некоторые методы / библиотеки, которые могут принимать некоторые параметры определения / конфигурации в качестве входных данных для разбиения строк unicode на такие куски слога. Не уверен, хотя! Оцените, если кто-то может поделиться тем, как они решили эту проблему, используя любой коммерчески доступный или открытый исходный код методы.
Comments