我已经阅读和听说C++11支持Unicode。几个相关问题:
- C++标准库对Unicode的支持如何?
std::string
是否能够正常工作?- 我该如何使用它?
- 有哪些可能存在的问题?
我已经阅读和听说C++11支持Unicode。几个相关问题:
std::string
是否能够正常工作?它需要吗?可能不需要。 std :: string
作为一组 char
对象是好的。这很有用;唯一的烦恼是它是文本的一个非常低级别的视图,标准C ++不提供更高级别的视图。
我应该如何使用它?
将其用作 char
对象的序列;假装它是其他东西注定会带来痛苦。
潜在的问题在哪里?
到处都是?让我们看看...
字符串库
字符串库为我们提供了basic_string
,它只是标准称之为“char-like对象”的序列。我称它们为代码单元。如果您想获得文本的高级别视图,则不是您要寻找的内容。这是适用于序列化/反序列化/存储的文本视图。
它还提供了从C库中提取的一些工具,可用于弥合窄世界与Unicode世界之间的差距:c16rtomb
/ mbrtoc16
和c32rtomb
/ mbrtoc32
。
本地化库
本地化库仍然认为那些“类似字符的对象”中的一个等于一个“字符”。这当然是愚蠢的,使得除了一些小型Unicode子集(如ASCII)之外,很难使许多事情正常工作。<locale>
头文件中所谓的“便利接口”。template <class charT> bool isspace (charT c, const locale& loc);
template <class charT> bool isprint (charT c, const locale& loc);
template <class charT> bool iscntrl (charT c, const locale& loc);
// ...
template <class charT> charT toupper(charT c, const locale& loc);
template <class charT> charT tolower(charT c, const locale& loc);
// ...
wstring_convert
用于在给定的编码之间转换字符串。此转换涉及两种字符串类型,标准称为字节串和宽串。由于这些术语实际上很容易引起误解,我更喜欢分别使用“序列化”和“反序列化”。
要转换的编码由传递给wstring_convert
作为模板类型参数的codecvt(代码转换facet)决定。
wbuffer_convert
执行类似的功能,但作为包装字节序列化流缓冲区的宽反序列化流缓冲区。通过底层字节序列化流缓冲区进行任何I/O,并通过codecvt参数进行编码转换。写入序列化到该缓冲区,然后从中写入,读取读入缓冲区,然后从其中反序列化。
codecvt_utf8<char16_t>
和codecvt_utf8<wchar_t>
(其中sizeof(wchar_t) == 2
);codecvt_utf8<char32_t>
、codecvt<char32_t, char, mbstate_t>
和codecvt_utf8<wchar_t>
(其中sizeof(wchar_t) == 4
);codecvt_utf16<char16_t>
和codecvt_utf16<wchar_t>
(其中sizeof(wchar_t) == 2
);codecvt_utf16<char32_t>
和codecvt_utf16<wchar_t>
(其中sizeof(wchar_t) == 4
);codecvt_utf8_utf16<char16_t>
、codecvt<char16_t, char, mbstate_t>
和codecvt_utf8_utf16<wchar_t>
(其中sizeof(wchar_t) == 2
);codecvt<wchar_t, char_t, mbstate_t>
;codecvt<char, char, mbstate_t>
。其中有几个是有用的,但这里有很多令人尴尬的东西。
首先,命名方案非常混乱。
其次,有很多UCS-2支持。UCS-2是Unicode 1.0中的一种编码,因为它只支持基本多语言平面而在1996年被取代。我不知道委员会为什么认为集中精力在一个20多年前就已经被取代的编码上是有意义的。并不是说支持更多的编码是坏事,但在这里UCS-2出现得太频繁了。
我认为,char16_t
显然是用于存储UTF-16代码单元的。然而,标准的这一部分却持有不同看法。codecvt_utf8<char16_t>
与 UTF-16 没有任何关系。例如,wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
编译时没有问题,但无条件失败:输入将被视为 UCS-2 字符串 u"\xD83C\xDF4C"
,它无法转换为 UTF-8,因为 UTF-8 不能编码范围在 0xD800-0xDFFF 之间的任何值。char16_t
字符串。这很令人惊讶,因为这几乎是一种身份转换。更令人惊讶的是,codecvt_utf16<char16_t>
支持从 UTF-16 流反序列化为 UCS-2 字符串,但这实际上是一种有损转换。
UTF-16作为字节的支持非常好: 它支持从BOM检测字节序, 或在代码中显式选择字节序。它还支持产生有BOM和无BOM的输出。
还有一些更有趣的转换可能是缺失的。没有办法将UTF-16字节流或字符串反序列化成UTF-8字符串,因为UTF-8从未被支持为反序列化形式。
此外,狭窄/宽字符世界与UTF/UCS世界完全分离。旧式的狭窄/宽字符编码与任何Unicode编码之间都没有转换。
输入/输出库
可以使用I/O库使用上述描述的wstring_convert和wbuffer_convert工具读写Unicode编码的文本。我认为标准库的这部分不需要支持太多其他内容。
正则表达式库
我之前在Stack Overflow上讲过关于C++正则表达式和Unicode的问题。我不会在这里重复所有这些观点,但只是陈述一下C++正则表达式没有一级Unicode支持,这是最低限度,使它们可用而无需到处使用UTF-32。是的,就是这样。这是现有的功能。有很多Unicode功能是看不到的,如规范化或文本分割算法。就这样?
常见的选择:ICU和Boost.Locale。U+1F4A9。有没有办法在C++中获得更好的Unicode支持?
字节串,顾名思义,是一串字节,即char
对象。然而,与始终为wchar_t
对象数组的"宽字符串文字"不同,在此上下文中的"宽字符串"不一定是wchar_t
对象字符串。实际上,标准从未明确定义"宽字符串"的含义,因此我们只能从使用中猜测其含义。由于标准术语不准确且令人困惑,我将采用自己的术语,以便更加清晰。
如UTF-16等编码可以存储为char16_t
序列,然后没有字节序; 或者它们可以存储为字节序列,这些字节具有字节序(每个连续的字节对可以表示不同的char16_t
值,具体取决于字节序)。标准支持这两种形式。char16_t
序列在程序内部操作中更有用。字节序列是与外部世界交换这样的字符串的方式。我将使用的术语而不是"字节"和"宽"是"序列化"和"反序列化"。
‡ 如果你想说“但是Windows!”请先。自从Windows 2000以来,所有版本的Windows都使用UTF-16。
☦ 是的,我知道关于 großes Eszett (ẞ) 的事情,但即使你在一夜之间将所有德语区域设置为 ß 大写成 ẞ,仍然有许多其他情况会失败。尝试将 U+FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ 大写。没有ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ;它只是大写成两个 F。或者 U+01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ;没有预组合的大写字母;它只是大写成一个大写的 J 和一个结合音符。
wchar_t
会很愚蠢)。当然,除非这是一个恒等转换。整个过程感觉很糟糕。设计这个的人根本不知道自己在做什么,委员会还批准了它 :( - R. Martinho Fernandesstd::string
中(或者在char[]
或char*
中),因为Unicode NUL(U+0000)在UTF-8中是一个空字节,这是空字节在UTF-8中唯一的出现方式。因此,根据所有C和C++字符串函数,您的UTF-8字符串将被正确终止,并且您可以使用C++ iostreams(包括std::cout
和std::cerr
,只要您的语言环境是UTF-8)。std::string
获取UTF-8中的代码点长度。 std::string::size()
会告诉您以字节为单位的字符串长度,当您处于UTF-8的ASCII子集内时,它才等于代码点数。std::string
可以很好地嵌入空字符,并被投放到 iostreams 中。 - R. Martinho Fernandesc_str()
,因为size()
仍然可以工作。只有那些无法处理嵌入空字符(像大多数C世界中的API)的不正常的API才会出现问题。 - R. Martinho Fernandesc_str()
,因为c_str()
应该返回数据作为空终止的C字符串---由于C字符串不能有嵌入式空值,这是不可能的。 - uckelmanc_str()
现在仅返回与 data()
相同的内容,即全部内容。需要传入大小的 API 可以使用它。不需要传入大小的 API 无法使用它。 - R. Martinho Fernandesc_str()
与 data()
的轻微区别在于,c_str()
确保结果后面跟着一个 NUL 类似对象,而我不认为 data()
这样做。不过,看起来现在 data()
也会这样做了。(当然,对于消耗大小而不是从终止符搜索推断大小的 API,这是不必要的。) - Ben VoigtC++11引入了几种新的字面值字符串类型(new literal string types),用于Unicode。
不幸的是,标准库对于非统一编码(如UTF-8)的支持仍然不好。例如,没有很好的方法来获取UTF-8字符串的长度(以代码点计算)。
std::string
可以轻松存储 UTF-8 字符串,但是例如 length
方法返回的是字符串中字节的数量而不是代码点的数量。 - Some programmer dudeñ
写成“LATIN SMALL LETTER N WITH TILDE”(U+00F1)(这是一个代码点),或者是“LATIN SMALL LETTER N”(U+006E)后跟着“COMBINING TILDE”(U+0303),这是两个代码点。 - Martin Bonner supports Monicastd::string
/std::wstring
的替代品。其目的在于填补仍然缺失的utf8字符串容器类的空白。char
形式编码。