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

16

我阅读了一些关于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()转换两种格式,或者使用更简单的包装函数。


哪些 std::basic_string 函数适用于 UTF-8? - dalle
UTF-32相对于wstring / Unicode有什么优势?顺便说一句,Visual Studio定义了“u16string”和“u32string”。 - Steve Townsend
@Steve:字符串中的一个元素是一个代码点,也就是一个字符吗? - sbi
2
@Steve:我应该提到平台独立通常是我的工作要求,而且我发现wchar_t大小(因此wstring)取决于实现。此外,我希望支持完整的Unicode字符范围,而UTF-32是我知道的在固定长度编码中实现这一点的最佳方式。它确实占用了很多空间,但我认为字符串大部分时间可以以UTF-8格式存储。 - nassar
2
看起来C++0x将把u32string定义为basic_string<char32_t>,而char32_t似乎等同于uint32_t(查看gcc/g++头文件)。因此,我应该称这些为u8string和u32string,并使用char32_t定义后者。 - nassar
显示剩余2条评论
3个回答

12
如果您计划仅传递字符串并且不需要检查它们,可以使用普通的std::string,但这是一个较差的选择。
问题在于大多数框架(甚至标准库)都愚蠢地强制实施编码。我认为这是很愚蠢的,因为编码只应该在接口上起作用,并且这些编码不适用于对数据进行内存操作。
此外,编码很简单(它是CodePoint->字节和相反的简单转置),而主要困难实际上是关于操作数据。
使用8位或16位,你有可能会中间切断一个字符,因为std::stringstd::wstring都不知道什么是Unicode字符。更糟糕的是,即使使用32位编码,也有将字符与适用于其的变音符号分离的风险,这也是愚蠢的。
就标准而言,C++中对Unicode的支持非常低劣。如果您真的希望操作Unicode字符串,则需要一个Unicode感知的容器。通常的方法是使用ICU库,尽管它的接口非常类似于C语言。但是,您将获取一切所需的内容,以便实际上使用多种语言进行Unicode工作。

1
我发现你关于变音符号的评论有点令人害怕。从某种意义上说,这与我正在尝试以相对简单的方式“正确”处理字符串密切相关。 - nassar
1
ICU在C++中有一个字符串类,它与std::string互操作。 - Steven R. Loomis
@Steven:我不应该在那里使用“managed”,抱歉。我是在谈论所有传递的char*指针,这对我来说似乎非常奇怪。对我来说,char*意味着该方法可能会修改传递的缓冲区,并且没有人知道谁负责指向的内存(我猜是调用者)。 - Matthieu M.
@Matthieu - 这些函数的内存所有权应该有明确的文档记录,如果您发现任何漏洞,可以提交错误报告。 - Steven R. Loomis
@Steven:你有没有想法为什么在ICU中,代码点被用作字符串的“默认”单位,而不是字形?使用字形难道不能解决Matthieu M.所描述的变音符号问题吗? - nassar
显示剩余4条评论

2
未指定字符串、宽字符串等必须使用哪种字符编码。通常的方式是在宽字符串中使用unicode。应该使用什么类型和编码取决于您的要求。
如果只需要从A传递数据到B,请选择具有UTF-8编码的std::string(不要引入新类型,只需使用std::string)。如果必须处理字符串(提取、连接、排序等),请在Windows上选择具有UCS2 / UTF-16(仅限BMP)编码的std::wstring,在Linux上选择具有UCS4 / UTF-32编码的std::wstring。 好处是固定大小:每个字符的大小为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 ,请编写自定义专门化程序。
新的C ++ 0x标准定义了wstring_convert <>和wbuffer_convert <>,用于使用std :: codecvt从窄字符集转换为宽字符集(例如UTF-8到UCS2)。 据我所知,Visual Studio 2010已经实现了这一点。

2
我故意避免使用UCS-2,因为在处理字符编码时,如果要做到正确并支持完整的Unicode,那么就应该做得好。同时,我正在寻找一种比ICU更少繁琐的通用用途解决方案。至于UTF-16,它似乎具有可变长度编码和使用大量内存的缺点。这就是为什么我建议结合使用UTF-8和UTF-32的原因。 - nassar
关于从std :: string派生的观点已被接受。谢谢! - nassar
1
我认为定义一个新类型并不是必须的,但很多人在代码中看到std::string时会倾向于忘记多字节字符并错误地使用字符位置。虽然可以在注释中传达它是UTF-8,但在类型名称中提醒似乎是有帮助的,因为像std::string::insert()这样的方法在我看来确实暗示了8位字符。 - nassar
是的,C++0x引入了char16_t和char32_t来在所有平台上具有特定大小的“宽字符”。定义新类型string8通常不会有太大问题,但如果您编写的是库或可重用代码,则可能会引起混淆。如果我需要构建一个包含3-4个库的项目,并且每个库都介绍自己的类型,那么我必须处理lib1::string8、lib2::ustring、lib3::utf8和我的自定义类型(或std::string)。仅查看这些类型并不能告诉我它是否只是std::string的另一个名称,还是一个完全不兼容的类,必须以特殊方式处理。 - cytrinox
1
如果您只需要在不同的UTF之间进行转换,并且已经使用了C++0x功能,则有一些新的codecvts可用,例如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()等编写自己的codecvts。 - cytrinox
显示剩余2条评论

1

链接会失效。如果包含内容会更好。 - joel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接