如何计算C中unicode字符串中的字符数

时间:2011-09-04 08:15:51

标签: c string unicode ascii

假设我有一个字符串:

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

鉴于我的编码是utf-8,这个字符串是12个字节长(三个hanzi字符各占三个字节,带有macron的拉丁字符是两个字节,'a'是一个字节:

strlen(theString) == 12

如何计算字符数?我怎样才能做相当于下标的内容:

theString[3] == "好"

我怎样才能切片,并捕捉这些字符串?

10 个答案:

答案 0 :(得分:29)

您只计算前两位未设置为10的字符(即,少于0x80或大于0xbf的所有字符。)

这是因为前两位设置为10的所有字符都是UTF-8连续字节。

有关编码的说明以及strlen如何处理UTF-8字符串,请参阅here

对于切片和切割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开始。

这将是一个不错的构建块,能够为您的目的充分操纵字符串。

答案 1 :(得分:17)

最简单的方法是使用像ICU

这样的库

答案 2 :(得分:13)

尝试尺寸:

#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] == "好"

答案 3 :(得分:8)

根据您对“角色”的概念,这个问题可能会或多或少地受到影响。

首先,您应该将字节字符串转换为一串unicode代码点。您可以使用ICU的iconv()执行此操作,但如果这是您唯一的操作,iconv()会更容易,而且它是POSIX的一部分。

您的unicode代码点字符串可能类似于以null结尾的uint32_t[],或者如果您有C1x,则为char32_t的数组。该数组的大小(即它的元素数,而不是它的大小,以字节为单位)是代码点的数量(加上终结符),这应该会给你一个很好的开始。

然而,“可打印字符”的概念相当复杂,您可能更愿意计算字形而非代码点 - 例如,a具有重音{{1可以表示为两个unicode代码点,或者表示为组合的遗留代码点^ - 两者都是有效的,并且unicode标准要求两者同等对待。有一个称为“规范化”的过程会将你的字符串变成一个确定的版本,但是有许多字素不能表达为单一的代码点,而且通常没有办法解决这个问题,并为你计算字形数据。

也就是说,由您来决定脚本的复杂程度以及您希望如何彻底对待它们。转换为unicode代码点是必须的,除此之外的一切都由您自行决定。

如果您决定需要ICU,请随时提出有关ICU的问题,但请先随意探索更为简单的â

答案 4 :(得分:2)

在现实世界中,theString[3]=foo;不是一项有意义的操作。为什么你想用不同的字符替换字符串中特定位置的字符?当然没有自然语言文本处理任务,这项操作是有意义的。

计算字符也不太可能有意义。 “á”中有多少个字符(对于你的“字符”的想法)? “á”怎么样?那么“གི”怎么样?如果您需要此信息来实现某种文本编辑,那么您将不得不处理这些难题,或者只使用现有的库/ gui工具包。除非你是世界脚本和语言方面的专家,否则我会推荐后者,并认为你可以做得更好。

出于所有其他目的,strlen会准确地告诉您实际有用的信息:字符串占用多少存储空间。这是组合和分离字符串所需要的。如果您要做的就是组合字符串或将它们分开到特定的分隔符snprintf(如果您坚持,则为strcat),并且strstr就是您所需要的。

如果您想执行更高级别的自然语言文本操作,如大写,换行等,甚至更高级别的操作,如复数化,时态变化等,那么您将需要像ICU这样的库或者分别具有更高级别和语言能力的东西(并且与您正在使用的语言相关)。

同样,大多数程序对此类事物没有任何用处,只需要在不考虑自然语言的情况下组装和解析文本。

答案 5 :(得分:1)

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

这将计算UTF-8字符串中的字符...(在本文中找到:Even faster UTF-8 character counting

然而,我仍然难以切片和连接?!?

答案 6 :(得分:1)

通常我们应该为unicode字符使用不同的数据类型。

例如,您可以使用宽字符数据类型

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

注意L修饰符告诉该字符串由宽字符组成。

可以使用wcslen函数计算该字符串的长度,该函数的行为类似于strlen

答案 7 :(得分:1)

从上述答案中不清楚的一点是,为什么它不简单。每个字符都以某种方式编码 - 例如,它不必是UTF-8 - 并且每个字符可能具有多种编码,具有处理重音符号组合的不同方式等。规则非常复杂,并且因编码而异(例如,utf-8与utf-16)。

这个问题存在巨大的安全问题,因此必须正确完成。使用OS提供的库或众所周知的第三方库来操作unicode字符串;不要自己动手。

答案 8 :(得分:0)

几年前我做过类似的实施。但是我没有代码。

对于每个unicode字符,第一个字节描述了构造unicode字符所遵循的字节数。根据第一个字节,您可以确定每个unicode字符的长度。

我认为它是一个很好的UTF8库。 enter link description here

答案 9 :(得分:-1)

一系列代码点构成许多其他非西欧语言中的单个音节/字母/字符(例如:所有印度语)

所以,当你计算长度或找到子串时(肯定会找到子串的用例 - 让我们说玩一个刽子手游戏),你需要按音节推进音节,而不是按代码点代码点

因此,字符/音节的定义以及将字符串实际分解为“音节块”的位置取决于您正在处理的语言的性质。 例如,许多印度语(印地语,泰卢固语,卡纳达语,马拉雅拉姆语,尼泊尔语,泰米尔语,旁遮普语等)的音节模式可以是以下任何一种

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字符串分解为这样的音节块。虽然不确定!感谢是否有人可以分享他们如何使用任何商业或开源方法解决这个问题。