问题 C ++中的字符串和字符编码


我在C ++中阅读了一些关于字符串和字符编码的最佳实践的帖子,但我在寻找一种在我看来相当简单和正确的通用方法时遇到了一些困难。我可以就以下内容征求意见吗?我倾向于使用UTF-8和UTF-32,并定义类似于:

typedef std::string string8;
typedef std::basic_string<uint32_t> string32;

string8类将用于UTF-8,并且具有单独的类型只是对编码的提醒。另一种方法是将string8作为std :: string的子类,并删除那些不太适合UTF-8的方法。

当需要固定字符大小时,string32类将用于UTF-32。

UTF-8 CPP函数utf8 :: utf8to32()和utf8 :: utf32to8(),或甚至更简单的包装函数,将用于在两者之间进行转换。


13161
2017-10-16 20:12


起源

注意 string8 仍然是同一类型 std::string;它只是有一个不同的名字。 - James McNellis
哪一个 std::basic_string 功能 是 适合UTF-8? - dalle
UTF-32通过wstring / Unicode为您带来什么?顺便说一句Visual Studio定义 u16string 和 u32string。 - Steve Townsend
@Steve:我应该提到平台独立性通常是我工作的要求,我发现wchar_t size(因此wstring)取决于实现。另外,我想支持全范围的Unicode字符,而UTF-32是我所知道的最好的固定长度编码方式。它确实占用了大量空间,但我认为大多数时候字符串可以存储在UTF-8中。 - nassar
看起来C ++ 0x会将u32string定义为basic_string <char32_t>,而char32_t似乎等同于uint32_t(查看gcc / g ++头文件)。所以我应该调用这些u8string和u32string,并使用char32_t定义后者。 - nassar


答案:


如果你打算只传递字符串并且从不检查它们,你可以使用plain std::string 虽然这是一个穷人的工作。

问题是,大多数框架,甚至标准,都在内存中愚蠢地(我认为)强制编码。我说愚蠢,因为编码应该只对接口有影响,而那些编码不适合内存中的数据操作。

此外,编码很容易(它是一个简单的转置CodePoint - >字节和反向),而主要的困难实际上是操纵数据。

对于8位或16位,您可能会在中间切割字符,因为两者都没有 std::string 也不 std::wstring 知道Unicode字符是什么。更糟糕的是,即使使用32位编码,也存在将字符与适用于它的变音符号分开的风险,这也是愚蠢的。

因此,就标准而言,Unicode在C ++中的支持非常低。

如果您真的希望操作Unicode字符串,则需要一个支持Unicode的容器。通常的方法是使用 ICU 库,虽然它的界面真的是C-ish。但是,您将获得使用多种语言实际使用Unicode所需的一切。


10
2017-10-17 10:00



我发现你对变音符号的评论有点吓人。从某种意义上说,它与我正在尝试做的最相关,即以相对简单的方式“正确”处理字符串。 - nassar
@nassar:不幸的是它很可怕,因为我们缺乏适当的支持:'( - Matthieu M.
ICU(在C ++中的其他接口中)具有与std :: string互操作的C ++字符串类 - Steven R. Loomis
@Steven: icu-project.org/apiref/icu4c/classUnicodeString.html 我在其界面中考虑C-ish(与非托管内存的大量交互,使用 int32_t 未签名的更适合,...)虽然你提到,谢谢 StringPiece 它可以从一个非常顺利的创建 std::string。 - Matthieu M.
@Matthieu很多情况下使用int32_t,' - 1表示'使用u_strlen作为长度'。此外,UText采用64位文本偏移量。如果让UnicodeString管理内存,则没有“与非托管[??]内存的大量交互”。 - Steven R. Loomis


所描述的特征方法 这里 可能会有所帮助。这是一种古老但有用的技术。


1
2017-10-16 20:21





没有指定必须为字符串,wstring等使用什么字符编码。常见的方法是在宽字符串中使用unicode。应使用哪些类型和编码取决于您的要求。

如果您只需要将数据从A传递给B,请选择带有UTF-8编码的std :: string(不要引入新类型,只需使用std :: string)。如果必须使用字符串(extract,concat,sort,...),请选择std :: wstring,并在Windows上编码UCS2 / UTF-16(仅限BMP),在Linux上编辑UCS4 / UTF-32。 好处是固定大小:每个字符的大小为2(或UCS4为4)字节,而带UTF-8的std :: string返回错误的length()结果。

对于转换,您可以检查sizeof(std :: wstring :: value_type)== 2或4以选择UCS2或UCS4。我正在使用ICU库,但可能有简单的包装器库。

建议不要使用std :: string派生,因为basic_string不是为(缺少虚拟成员等)而设计的。如果你真的真的需要你自己的类型,比如std :: basic_string <my_char_type>为此写一个自定义的专门化。

新的C ++ 0x标准将wstring_convert <>和wbuffer_convert <>定义为使用std :: codecvt从窄字符集转换为宽字符集(例如UTF-8到UCS2)。 Visual Studio 2010已经实现了这个,afaik。


1
2017-10-16 20:45



我故意避免使用UCS-2,因为在我看来,如果一个人遇到处理字符编码的问题,那么也可以正确地做到并支持完整的Unicode。 (与此同时,我正在寻找一些比ICU更通用的东西。)至于UTF-16,似乎有可变长度编码和使用大量内存的缺点。这就是为什么我建议组合使用UTF-8和UTF-32。 - nassar
关于从std :: string派生的观点。谢谢! - nassar
我认为定义一个新类型根本不是必需的,但很多人在代码中看到std :: string会忘记多字节字符并错误地使用字符位置。它是UTF-8的事实可以在注释中传达,但在类型名称中有一个提醒看起来很有帮助,因为std :: string :: insert()等方法在我看来确实提出了8位字符。 - nassar
我刚刚读到C ++ 0x将u32string定义为basic_string <char32_t>。所以这对UTF-32来说应该是好的。 - nassar
为了完成,如果你只需要在不同的UTF之间进行转换,并且你已经使用了c ++ 0x特性,那么就会有一些新的代码,例如codecvt <char16_t,char,mbstate_t>和codecvt <char32_t,char,mbstate_t >将char(UTF-8)转换为UTF16 / 32。与std :: wstring_convert和std :: wbuffer_convert一起,您可以轻松地在UTF之间进行转换而无需任何附加库。如果你需要转换其他字符集,你可以使用linux上的iconv()和windows上的MultiByteToWideChar()&Co。编写自己的代码。 - cytrinox