我的平台是Mac和C ++ 11(或更高版本)。我是一名C ++初学者,正在处理一个处理中文和英文的个人项目。 UTF-8是此项目的首选编码。
我在Stack Overflow上阅读了一些帖子,其中许多人建议在处理UTF-8时使用std::string
并避免使用wchar_t
,因为现在UTF-8没有char8_t
。
但是,他们都没有谈论如何正确处理str[i]
,std::string::size()
,std::string::find_first_of()
或std::regex
等功能,因为这些功能在面对UTF时通常会返回意外结果-8。
我应该继续使用std::string
还是切换到std::wstring
?如果我应该留在std::string
,那么处理上述问题的最佳做法是什么?
答案 0 :(得分:68)
Unicode是一个庞大而复杂的主题。我不想在那里跋涉太深,但是需要一个快速的词汇表:
这是Unicode的基础。 Code Point和Grapheme Cluster之间的区别可能大部分被掩盖,因为对于大多数现代语言,每个“字符”都映射到一个代码点(常用字母+变音符组合有专用的重音形式)。不过,如果你冒险使用表情符号,旗帜等......那么你可能需要注意区别。
然后,必须编码一系列Unicode代码点;常见的编码是UTF-8,UTF-16和UTF-32,后两种以Little-Endian和Big-Endian形式存在,总共有5种常见编码。
在UTF-X中,X是代码单元的位大小,每个代码点表示为一个或多个代码单元,具体取决于其大小:
std::string
和std::wstring
。std::wstring
(Windows上wchar_t
只有16位);请改用std::u32string
(又名std::basic_string<char32_t>
)。std::string
或std::wstring
)独立于磁盘上的表示形式(UTF-8,UTF-16或UTF-32),因此请准备好进行转换在边界(阅读和写作)。wchar_t
确保代码单元代表完整的代码点,但它仍然不代表完整的字形集群。如果您只是阅读或撰写字符串,那么std::string
或std::wstring
应该没有什么问题。
当您开始切片和切块时,麻烦就开始了,那么您必须注意(1)代码点边界(UTF-8或UTF-16)和(2)Grapheme Clusters边界。前者可以自己轻松处理,后者需要使用Unicode感知库。
std::string
或std::u32string
?如果性能受到关注,std::string
可能因其较小的内存占用而表现更好;虽然大量使用中国人可能会改变这笔交易。一如既往,简介。
如果Grapheme Clusters不是问题,那么std::u32string
具有简化事物的优点:1代码单元 - &gt; 1代码点意味着您不会意外拆分代码点,std::basic_string
的所有功能都可以开箱即用。
如果您与使用std::string
或char*
/ char const*
的软件进行交互,请坚持std::string
以避免来回转换。否则这将是一种痛苦。
std::string
中的UTF-8。 UTF-8在std::string
中实际上运作良好。
大多数操作都是开箱即用的,因为UTF-8编码是自同步的,并且与ASCII向后兼容。
由于代码点的编码方式,寻找代码点不会意外地匹配另一个代码点的中间位置:
str.find('\n')
有效,str.find("...")
用于逐字节匹配 1 ,str.find_first_of("\r\n")
如果搜索ASCII字符则。同样,regex
应该开箱即用。由于字符序列("haha"
)只是一个字节序列("哈"
),因此基本搜索模式应该是开箱即用的。
但要注意字符类(例如[:alphanum:]
),因为它取决于正则表达式的风格和实现,它可能与Unicode字符匹配,也可能不匹配。
同样,要小心将转发器应用于非ASCII“字符”,"哈?"
可能只考虑最后一个字节是可选的;在这种情况下,使用括号清楚地描述重复的字节序列:"(哈)?"
。
1 查找的关键概念是规范化和整理;这会影响所有比较操作。 std::string
将始终逐字节地比较(并因此排序),而不考虑特定于语言或用法的比较规则。如果需要处理完全规范化/整理,则需要一个完整的Unicode库,例如ICU。
答案 1 :(得分:8)
std::string
和朋友编码无关。 std::wstring
和std::string
之间的唯一区别是std::wstring
使用wchar_t
作为单个元素,而不是char
。对于大多数编译器,后者是8位。前者应该足够大以容纳任何unicode字符,但实际上在某些系统上它不是(微软的编译器,例如,使用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范围内的任何字节。
答案 2 :(得分:8)
std::string
和std::wstring
都必须使用UTF编码来表示Unicode。特别是在macOS上,std::string
是UTF-8(8位代码单元),std::wstring
是UTF-32(32位代码单元);请注意,wchar_t
的大小取决于平台。
对于两者,size
跟踪代码单元的数量而不是代码点或字形集群的数量。 (代码点是一个名为Unicode的实体,其中一个或多个构成一个字形集群。字形集群是用户与之交互的可见字符,如字母或表情符号。)
虽然我不熟悉中文的Unicode表示,但是当你使用UTF-32时,代码单元的数量通常非常接近字形集群的数量。然而,显然,这需要使用多达4倍的内存。
最准确的解决方案是使用Unicode库(如ICU)来计算您所追求的Unicode属性。
最后,人类语言中不使用组合字符的UTF字符串通常与find
/ regex
非常相似。我不确定中文,但英文就是其中之一。
答案 3 :(得分:0)
考虑到升级到C ++ 20和std::u8string
,这是我们自2019年以来拥有UTF-8最好的东西。没有标准的库工具可以访问单个代码点或字素簇,但至少您的类型足够强大,至少可以说它是真正的UTF-8。
答案 4 :(得分:0)
我应该继续使用 std::string
还是切换到 std::wstring
?
我建议使用 std::string
,因为 wchar_t
是不可移植的,并且 C++20 char8_t
在标准中的支持很差,并且根本不受任何系统 API 支持(并且会可能永远不会因为兼容性原因)。在包括 macOS 在内的大多数平台上,您使用的普通 char
字符串已经是 UTF-8。
大多数标准字符串操作使用 UTF-8,但使用 code units。如果您想要更高级别的 API,则必须使用其他内容,例如向 Boost 提议的 the text library。