如何在C ++中的UTF-8上正确使用std :: string?

我的平台是Mac和C ++ 11(或更高版本)。 我是C ++初学者,并且从事处理中文和英文的个人项目。 UTF-8是此项目的首选编码。

我读了一些关于Stack Overflow的文章,其中许多文章建议在处理UTF-8时使用std::string,并避免使用std::wstring,因为目前没有std::string用于UTF-8。

但是,他们都没有谈论如何正确处理诸如std::stringstd::wstringstd::stringstd::regex之类的函数,因为这些函数在面对UTF-8时通常会返回意外结果。

我应该继续使用std::string还是切换到std::wstring? 如果我留在std::string,那么解决上述问题的最佳实践是什么?

stackunderflow asked 2019-11-15T16:24:41Z
4个解决方案
77 votes

Unicode词汇表

Unicode是一个庞大而复杂的主题。 我不希望在那儿走得太深,但是有必要提供一个简短的词汇表:

  1. 代码点:代码点是Unicode的基本构建块,代码点只是映射为含义的整数。 整数部分可容纳32位(实际上是24位),其含义可以是字母,变音符号,空格,符号,笑脸,半个旗标,甚至可以是“ 下一部分从右到左读取”。
  2. 字素簇:字素簇是语义相关的代码点的组,例如unicode中的标志是通过关联两个代码点来表示的; 孤立的这两个中的每一个都没有意义,但是在一个词素簇中关联在一起,它们代表一个标志。 在某些脚本中,字素簇还用于将字母与变音符号配对。

这是Unicode的基础。 代码点和字素簇之间的区别大部分可以被掩盖,因为对于大多数现代语言而言,每个“字符”都映射到单个代码点(对于常用的字母和变音符号组合,有专用的重音形式)。 不过,如果您冒险使用笑脸,旗帜等,那么您可能必须注意区别。


UTF入门

然后,必须对一系列Unicode代码点进行编码; 通用编码为UTF-8,UTF-16和UTF-32,后两种以Little-Endian和Big-Endian形式存在,总共有5种通用编码。

在UTF-X中,X是代码单位的大小,每个代码点根据其大小表示为一个或几个代码单位:

  • UTF-8:1到4个代码单位,
  • UTF-16:1或2个代码单位,
  • UTF-32:1个代码单位。

std::string"(哈)?"

  1. 如果您担心可移植性,请不要使用std::string"(哈)?"在Windows上仅为16位); 请改用"哈"(aka std::string)。
  2. 内存中表示形式(std::string"(哈)?")与磁盘上表示形式(UTF-8,UTF-16或UTF-32)无关,因此请做好在边界转换(读取和写入)的准备。
  3. 虽然32位std::string可以确保一个代码单元代表一个完整的代码点,但仍然不能代表一个完整的字素簇。

如果仅阅读或编写字符串,则std::string"(哈)?"应该没有什么问题。

当您开始切片和切块时,麻烦就开始了,那么您必须注意(1)代码点边界(在UTF-8或UTF-16中)和(2)字素簇边界。 前者可以很容易地自己处理,后者需要使用Unicode感知库。


采摘std::string"(哈)?"

如果需要考虑性能,则std::string可能会因其较小的内存占用而表现更好。 尽管大量使用中文可能会改变交易。 一如既往,简介。

如果Grapheme Clusters没问题,那么std::string具有简化功能的优势:1个代码单位-> 1个代码点意味着您不会意外地拆分代码点,并且"(哈)?"的所有功能都可以立即使用。

如果您与采用std::string"(哈)?"/"哈"的软件对接,请坚持使用std::string,以避免来回转换。 否则会很痛苦。


UTF-8,格式为std::string

UTF-8实际上可以在std::string中很好地工作。

大多数操作都是开箱即用的,因为UTF-8编码是自同步的并且与ASCII向后兼容。

由于代码点的编码方式不同,因此寻找代码点不会偶然匹配另一个代码点的中间部分:

  • std::string工程,
  • std::string用于逐字节匹配字节,
  • 如果搜索ASCII字符,则std::string可以工作。

同样,std::string应该开箱即用。 由于字符序列("(哈)?")只是字节序列("哈"),因此基本搜索模式应该可以立即使用。

但是,请警惕字符类(例如std::string),因为取决于正则表达式的风格和实现,它可能匹配Unicode字符,也可能不匹配。

同样,请小心将中继器应用于非ASCII“字符”,std::string可能仅将最后一个字节视为可选字节; 在这种情况下,请使用括号清楚地描述重复的字节序列:"(哈)?"

1查找的关键概念是归一化和归类; 这会影响所有比较操作。 std::string将始终逐字节比较(并因此进行排序),而不考虑特定于语言或用法的比较规则。 如果需要处理完整的规范化/归类,则需要完整的Unicode库,例如ICU。

Matthieu M. answered 2019-11-15T16:28:56Z
9 votes

findregex都必须使用UTF编码来表示Unicode。 特别是在macOS上,std::string是UTF-8(8位代码单元),std::wstring是UTF-32(32位代码单元); 请注意wchar_t的大小取决于平台。

对于两者,find跟踪代码单位的数量,而不是代码点或字素簇的数量。 (一个代码点是一个名为Unicode的实体,其中一个或多个组成一个字素簇。字素簇是用户与之交互的可见字符,例如字母或表情符号。)

尽管我对中文的Unicode表示不熟悉,但是很有可能在使用UTF-32时,代码单位的数量通常非常接近字素簇的数量。 但是,显然,这是以使用多达4倍的内存为代价的。

最准确的解决方案是使用Unicode库(例如ICU)来计算您要使用的Unicode属性。

最后,人类语言中不使用组合字符的UTF字符串通常可以很好地与find/regex配合使用。 我不确定中文,但是英语就是其中之一。

zneak answered 2019-11-15T16:30:00Z
8 votes

std::string和朋友与编码无关。 std::wstringstd::string之间的唯一区别是std::wstring使用wchar_t作为单个元素,而不是char。对于大多数编译器,后者是8位。 前者应该足够大以容纳任何unicode字符,但实际上在某些系统上却不是(例如Microsoft的编译器使用16位类型)。 您不能在std::wstring中存储UTF-8; 那不是设计的目的。 它被设计为等同于UTF-32-一个字符串,其中每个元素都是单个Unicode代码点。

如果要按Unicode代码点或组合的Unicode字形(或其他方式)为UTF-8字符串编制索引,计算Unicode代码点或其他Unicode对象中UTF-8字符串的长度,或按Unicode代码点查找, 除了标准库外,还需要使用其他东西。 ICU是该领域的图书馆之一; 可能还有其他。

可能值得注意的是,如果要搜索ASCII字符,则通常可以将UTF-8字节流视为逐字节对待。 每个ASCII字符在UTF-8中的编码方式与在ASCII中的编码方式相同,并且保证UTF-8中的每个多字节单元均不包含ASCII范围内的任何字节。

James Picone answered 2019-11-15T16:30:55Z
2 votes

考虑升级到C ++ 20和std::u8string,这是我们自2019年以来持有UTF-8最好的东西。 没有标准的库工具可以访问单个代码点或字素簇,但是至少您的类型足够强大,至少可以说它是真正的UTF-8。

Lyberta answered 2019-11-15T16:31:26Z
translate from https://stackoverflow.com:/questions/50403342/how-do-i-properly-use-stdstring-on-utf-8-in-c