如何统计C中的Unicodestring中的字符
比方说,我有一个string:
char theString[] = "你们好āa";
考虑到我的编码是utf-8,这个string是12个字节长(三个汉字字符是三个字节,拉丁字符与macron是两个字节,而'a'是一个字节:
strlen(theString) == 12
我如何计算字符的数量? 我怎样才能做相当于下标:
theString[3] == "好"
我怎么能切片,并猫这样的string?
您只计算前两位未设置为10
的字符(即小于0x80
或大于0xbf
)。
这是因为前两位设置为10
的所有字符都是UTF-8连续字节。
请参阅这里了解编码的说明以及strlen
如何在UTF-8string上工作。
对于UTF-8string的切片和切块,基本上必须遵循相同的规则。 任何以0
或11
开头的字节都是UTF-8编码点的开始,其他的都是连续字符。
如果您不想使用第三方库,最好的办法就是简单地提供以下function:
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;
分别得到:
- string左边的
sz
UTF-8字节。 - 一个string的
sz
UTF-8字节,从pos
开始。 - string的UTF-8字节的其余部分,从
pos
开始。
这将是一个体面的积木,能够为您的目的充分操纵string。
最简单的方法是使用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] == "好"
根据你的“性格”的概念,这个问题可以或多或less地涉及。
首先,您应该将您的字节string转换为一个unicode代码点的string。 你可以用ICU的iconv()
来做到这一点,但是如果这是你做的唯一的事情,那么iconv()
就更容易了,它是POSIX的一部分。
你的unicode代码点的string可能是一个空终止uint32_t[]
,或者如果你有C1x,一个char32_t
数组。 该数组的大小(即其元素的数量,而不是字节的大小)是码点的数量(加终止符),这应该给你一个很好的开始。
然而,“可打印字符”的概念相当复杂,您可能更喜欢计算字形而不是代码点 – 例如,带有口音的^
可以表示为两个unicode代码点,或者作为组合的遗留代码点,两者都是有效的,并且这两者都是unicode标准所要求的一样对待。 有一个叫做“规范化”的过程,它把你的string变成一个明确的版本,但是有很多不能被表示成单个代码点的字形,一般来说,没有办法理解这一点,并为你计算字形。
也就是说,由您来决定脚本的复杂程度,以及如何彻底对待它们。 转换为unicode代码点是必须的,除此之外的一切都是您自己决定的。
如果您决定需要ICU,请随时提问ICU问题,但请随时先探索极其简单的iconv()
。
一般来说,我们应该为unicode字符使用不同的数据types。
例如,您可以使用宽字符数据types
wchar_t theString[] = L"你们好āa";
注意L修饰符,它告诉string由宽字符组成。
该string的长度可以使用wcslen
函数计算,该函数的行为类似于strlen
。
在现实世界中, theString[3]=foo;
不是一个有意义的操作。 为什么要用一个不同的字符replacestring中特定位置的字符? 当然没有自然语言文本处理任务,这个操作是有意义的。
计数angular色也不太可能有意义。 “á”中有多less个字符(用于“字符”的概念)? “á”怎么样? 现在怎么样“གི”? 如果您需要这些信息来实现某种文本编辑,那么您将不得不处理这些难题,或者只使用现有的库/ gui工具包。 我会推荐后者,除非你是世界脚本和语言的专家,并认为你可以做得更好。
为了所有其他目的, strlen
会准确告诉您实际上有用的信息:string需要多less存储空间。 这是组合和分隔string所需要的。 如果所有你想要做的是组合string或将它们分隔开特定的分隔符, snprintf
(或strcat
如果你坚持…)和strstr
是你所需要的。
如果你想进行更高层次的自然语言文本操作,比如大写,换行等,或者更高层次的操作,比如多元化,时态变化等等,那么你需要像ICU这样的库或者某种东西更高层次和语言能力(特定于您正在使用的语言)。
再说一次,大多数程序对这种东西没有任何用处,只需要在不考虑自然语言的情况下汇编和parsing文本。
while (s[i]) { if ((s[i] & 0xC0) != 0x80) j++; i++; } return (j);
这将统计字符在一个UTF-8string…(本文中find: 更快的UTF-8字符计数 )
然而,我仍然难以切片和连接?!?
有一点从上面的答案中不清楚,为什么这不简单。 每个字符都以某种方式进行编码 – 例如,它不一定是UTF-8,而且每个字符可能有多种编码方式,处理重音的组合等等。规则非常复杂,因编码而异(例如,utf-8与utf-16)。
这个问题具有巨大的安全性问题,所以这是正确的。 使用操作系统提供的库或着名的第三方库来操作Unicodestring; 不要推出自己的。
几年前我做了类似的实施。 但我没有与我的代码。
对于每个unicode字符,第一个字节描述构成unicode字符的字节数。 根据第一个字节,您可以确定每个Unicode字符的长度。
我认为它是一个很好的UTF8库。 在这里input链接描述
许多其他非西欧语言中的代码点序列构成单音节/字母/字符(例如:所有印度语言)
所以,当你计算长度或者find子字符串时(确实存在find子string的用例 – 让我们说玩hang子手游戏),你需要按音节推进音节,而不是按代码点按代码点。
因此,字符/音节的定义以及实际上将string分解为“音节块”的位置取决于您所处理语言的性质。 例如,许多印度语(印度语,泰卢固语,卡纳达语,马拉雅拉姆语,尼泊尔语,泰米尔语,旁遮普语等)的音节模式可以是以下任何一种
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
您需要parsingstring并查找上述模式来破坏string并查找子string。
我不认为有可能有一个通用的方法,可以以任何unicodestring(或代码点序列)以上述方式奇迹般地破坏string – 因为适用于一种语言的模式可能不适用于另一个字母;
我想可能有一些方法/库,可以采取一些定义/configuration参数作为input将Unicodestring分割成这样的音节块。 虽然不确定! 欣赏是否有人可以分享他们如何使用任何商业或开源方法解决这个问题。