处理UTF-8字符串

5
据我所知,Linux使用UTF-8编码。这意味着我可以使用std::string来处理字符串,对吗?只是编码将是UTF-8。
现在,在UTF-8中,我们知道有些字符是1个字节,有些是2个、3个字节等。我的问题是:如何在Linux上使用C++处理UTF-8编码的字符串?
特别地,如何获得字符串的长度(以字节或字符数表示)?如何遍历字符串等等。
我之所以问这个问题,是因为如我所说,在UTF-8中,字符可能不止一个字节,对吧?因此,显然myString[7]myString[8]可能不指代两个不同的字符。另外,UTF-8字符串的长度为十个字节,并不能说明它的字符数,对吧?

3
不可以! - Konrad Rudolph
1
@Konrad Rudolph:我同意在传输/存储时应该使用UTF-8。但是,如果您需要随机访问,则无法使用UTF-8。在这种情况下,UTF-32是正确的选择。 - Tobias Brandt
8
你的论点是正确的,但你的结论不正确。正确答案是:不要使用随机访问。你不需要它。即使你以某种方式需要它,UTF-32 也不是答案,因为你仍然需要处理规范化、组合字符等问题。仅仅使用 wstring 并不能解决这个问题,它只是忽略了这些问题。 - Konrad Rudolph
2
你能解释一下为什么你需要对“字符”进行随机访问吗?(你可能需要提供自己对“字符”的定义以便更好地交流)这样的解释可能有助于找到更好的解决方案,或者让你意识到你并不真正需要它。 - R. Martinho Fernandes
1
@kvv 哪一个?通常你先搜索它并保持某种迭代器/索引到正确的位置。你不会跳到“第三个字符”并替换它。 - R. Martinho Fernandes
显示剩余9条评论
5个回答

6
您不能使用std :: string 处理UTF-8。 尽管名称是如此,但string仅是(multi-)字节的容器。 它不是用于文本存储的类型(除了字节缓冲区明显可以存储任何对象,包括文本之外)。它甚至不存储字符(char是一个字节,而不是一个字符)。
如果您想要实际处理(而不仅仅是存储)Unicode字符,则需要走出标准库。传统上,这是由诸如ICU等库完成的。
但是,虽然这是一种成熟的库,但其C ++接口很差。在Ogonek中采取了现代方法。它还没有得到很好的发展,并且仍在进行中,但提供了一个非常好的接口。

UTF-8不是一种多字节编码吗?虽然我理解你的真实意图,但不要说“无法处理”,因为这取决于上下文。 - user948581
4
它如何取决于上下文?我同意std :: string可以包含UTF-8。但是,不能处理它。 - Konrad Rudolph
让我们不要把这变成一个关于“处理”定义的无意义争论。这里是苹果,那里是橙子。 - user948581
UTF-8中基于字节的搜索不会产生误报,这是它相对于其他传统编码的优势。在"ä"中找不到"ä"是否为假阴性取决于您正在进行的操作,但任何声称符合Unicode标准的过程都必须将这两个字符视为等效。 - R. Martinho Fernandes
@sashoalm 只是顺便提一下,西里尔字母表之所以是分开的,即使有些字母“相同”,也是因为合并它们会引起一些麻烦。考虑小写转换。西里尔字母 В 转换为 в,而拉丁字母 B 转换为 b。(而且 ve 只在视觉上匹配 B;通常是 be (Б) 对应于 B)。'A' 在两种字母表中都很好匹配,但我想他们决定为了一致性将它们全部单独编码。 - R. Martinho Fernandes
显示剩余10条评论

3
您可能需要在操作UTF-8编码字符串之前将其转换为某种固定宽度编码。但这取决于您想要做什么。
要获取UTF-8字符串的字节长度,只需使用str.size()。要获取字符长度稍微有点困难,但可以通过忽略字符串中任何值大于等于0x80且小于0xC0的字节来获得。在UTF-8中,这些值始终是尾随字节。因此,统计这样的字节数量并从字符串的大小中减去即可。
上述方法忽略了组合字符的问题。这确实取决于您对字符的定义。

1
特别是你的计数算法将返回代码点的数量,而不是字符。我们英语世界的人往往没有注意到这种区别。 - Mark Ransom
@john:谢谢。但是这个文档:http://www.cplusplus.com/reference/string/string/size/ 说size返回的是字符数,而不是字节数,那么它如何正确计算字节数呢? - user2793162
@dmcr_code 混淆的原因是因为字符有不同的定义。在UTF-8字符串中,一个字符可以超过一个字节。你引用的参考文献假设一个字符是一个字节,但对于你来说这并不正确。我的答案是正确的,除了Mark Ransom提到的那一点,他使用了另一种字符定义。为什么不写一些测试代码呢? - john
@dmcr_code std::string 没有内置的理解UTF-8编码。它怎么能知道你在使用UTF-8呢?因此,std::string::size 不可能返回字符串中UTF-8字符的数量。 - john
是的,它会,字节只是字节。无论您的字符串是UTF-8还是其他任何格式都没有影响。 - john
显示剩余3条评论

2
这里涉及到多个概念:
  1. UTF-8编码的字节数长度
  2. 使用的Unicode代码点数(= UTF-8字节中0x80..0xbf范围之外的字节数)
  3. 占用的字形数(在西方语言中称为“字符”)
  4. 显示时所占用的屏幕空间
通常,您只关心1.(内存需求)和4.(显示),其他没有实际应用。
可以从渲染上下文中查询屏幕空间的数量。请注意,这可能会根据上下文而改变(例如,阿拉伯字母在单词的开头和结尾处会改变形状),因此,如果您正在进行文本输入,则可能需要执行额外的技巧以给用户提供一致的体验。

我不同意。一个合适的UTF-8字符串应该让你可以访问1、2和3,因为它们在处理文本时都很重要,而4则与字符串无关。 - RecursiveExceptionException
@NullExceptionPointer,2和3对于字符串并没有提供任何确切的信息,除非我对所使用的语言进行了假设。 - Simon Richter
好的,我假设使用utf-8编码的Unicode,但字符串可以包含任何内容,这样做相当愚蠢。 - RecursiveExceptionException
@NullExceptionPointer,Unicode现在可以编码许多不同的内容,例如所有的表情符号,其中一些具有附加修饰符或国家代码,这些代码始终是两个Unicode代码点,例如 以U+1F1E8 U+1F1ED编码。如果您想知道有多少字形,您需要一个完整的Unicode表。 - Simon Richter

1
我正在使用libunistring库,它可以帮助您处理所有问题。

例如,这是一个简单的字符串长度(以utf-8字符计算)函数:
size_t my_utf8_strlen(uint8_t *str) {
    if (str == NULL) return 0;
    if ((*str) == 0) return 0;

    size_t length = 0;
    uint8_t *current = str;
    // UTF-8 character.
    ucs4_t ucs_c = UNINAME_INVALID;

    while (current && *current) {
        current = u8_next(&ucs_c, current);
        length++; 

        // Broken character.
        if (ucs_c == UNINAME_INVALID || ucs_c == 0xfffd) 
        return length - 1;
    }

    return length;
}

// Use case
std::string test;

// Loading some text in `test` variable.
// ...

std::cout << my_utf8_strlen(&test[0]) << std::endl;

0

你可以根据第一个字节的前x位来确定它的编码方式: UTF-8, 描述


你可以确定什么?没有Unicode字符数据库,无法知道UTF-8序列中有多少个字符。 - Nikos C.
对于他的问题:“特别是:如何获取字符串的长度(以字节或字符数为单位)?如何遍历字符串?等等。”,我建议您逐个遍历字节。如果您遇到的第一个字节是11110xxx格式的,则接下来遇到的3个字节属于同一个字符串。看到3个字节后,就开始一个新字符,重复以上步骤。 - Kent Munthe Caspersen
@NikosC。确实,您只需要每个字节的前几位来确定它是否是一个连续项。这足以计算代码点的长度(尽管这仍然不一定是单词的长度)。 - Konrad Rudolph
1
不是这样的。例如,字符串“άβ”可能被识别为三个字符长。实际上,它只有两个字符。这是因为“ά”可以由两个字符α'组成。你不能仅仅通过查看位来确定一个字符是否实际上代表另一个字符的一部分。你需要一个数据库来解决这个问题。因此,像ICU这样的库已经被开发出来,可以为你进行查找。 - Nikos C.
2
字节数量!=代码点数量!=字符数量 - thecoshman
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接