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

jsj*_*jsj 56 c string unicode ascii

让我们说我有一个字符串:

char theString[] = "????a";
Run Code Online (Sandbox Code Playgroud)

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

strlen(theString) == 12
Run Code Online (Sandbox Code Playgroud)

我如何计算字符数?我该如何做相当于下标的内容,以便:

theString[3] == "?"
Run Code Online (Sandbox Code Playgroud)

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

pax*_*blo 29

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

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

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

对于切片和切割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;
Run Code Online (Sandbox Code Playgroud)

得到,分别:

  • sz字符串的左UTF-8字节.
  • sz字符串的UTF-8字节,从pos.
  • 字符串的其余UTF-8字节,从pos.

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

  • 注意:这会忽略[UAX#29](http://www.unicode.org/reports/tr29/)中描述的字素簇,即“नि”应该被视为单个文本单元,但会给出长度为 2,使用此答案中的方法。 (3认同)

use*_*019 17

最简单的方法是使用像ICU这样的库

  • @ trideceth12:在很多情况下,你实际上想要访问字形集群,而不是字符; 并且从头开始实现比仅仅解码UTF-8更为复杂,因此使用库可能是一个好主意 (6认同)
  • @Mark ..我问了几个关于ICU的问题.人们大多回答说,简单的操作是不必要的.http://stackoverflow.com/questions/7294447/how-to-get-started-with-icu-in-c (2认同)

Mat*_*ner 15

试试这个尺码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

样品运行:

matt@stanley:~/Desktop$ echo -n ????a | ./utf8ops 
utf8len=5
utf8slice[2:3]=?
utf8slice[3:4]=?
Run Code Online (Sandbox Code Playgroud)

请注意,您的示例有一个错误. theString[2] == "?"


Ker*_* SB 9

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

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

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

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

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

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