![](/img/trans.png)
[英]C++: How do I write the contents of std::string to a UTF-8 encoded file?
[英]How do I properly use std::string on UTF-8 in C++?
我的平台是 Mac。 我是一个 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
,那么处理上述问题的最佳做法是什么?
Unicode 是一个庞大而复杂的话题。 我不想在那里涉水太深,但是需要一个快速的词汇表:
这是Unicode的基础。 Code Point 和 Grapheme Cluster 之间的区别大多可以掩盖,因为对于大多数现代语言,每个“字符”都映射到单个 Code Point(对于常用的字母 + 变音符号组合有专用的重音形式)。 不过,如果您尝试使用笑脸、旗帜等……那么您可能需要注意区别。
然后,必须对一系列 Unicode 代码点进行编码; 常见的编码有 UTF-8、UTF-16 和 UTF-32,后两者有 Little-Endian 和 Big-Endian 两种形式,共有 5 种常见编码。
在 UTF-X 中,X 是Code Unit 的比特大小,每个 Code Point 表示为一个或多个 Code Unit,具体取决于其大小:
std::string
和std::wstring
。std::wstring
( wchar_t
在 Windows 上只有 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 Code Unit -> 1 Code Point 意味着你不会不小心拆分 Code Points, 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。
std::string
和朋友是编码不可知的。 std::wstring
和std::string
之间的唯一区别是std::wstring
使用wchar_t
作为单个元素,而不是char
。 对于大多数编译器,后者是 8 位的。 前者应该足够大以容纳任何 unicode 字符,但实际上在某些系统上并非如此(例如,Microsoft 的编译器使用 16 位类型)。 您不能将 UTF-8 存储在std::wstring
; 这不是它的设计目的。 它被设计为等效于 UTF-32 - 一个字符串,其中每个元素都是一个 Unicode 代码点。
如果你想通过 Unicode 代码点或组合的 unicode 字形(或其他东西)索引 UTF-8 字符串,计算 Unicode 代码点或其他一些 unicode 对象中 UTF-8 字符串的长度,或者通过 Unicode 代码点查找,你是将需要使用标准库以外的东西。 ICU是该领域的图书馆之一; 可能还有其他人。
可能值得注意的一点是,如果您正在搜索 ASCII 字符,您通常可以将 UTF-8 字节流视为逐字节处理。 每个 ASCII 字符在 UTF-8 中的编码方式与在 ASCII 中相同,并且 UTF-8 中的每个多字节单元都保证不包含 ASCII 范围内的任何字节。
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
配合得很好。 我不确定中文,但英文是其中之一。
考虑升级到 C++20 和std::u8string
,这是我们在 2019 年拥有的最好的东西来保存 UTF-8。 没有标准的库工具来访问单个代码点或字素簇,但至少你的类型足够强大,至少可以说它是真正的 UTF-8。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.