87

我在 C++ 社区中看到很多人(尤其是 freenode 上的##c++)对wstringsandwchar_t的使用以及它们在 windows api 中的使用感到不满。和 到底有什么“错误” wchar_twstring如果我想支持国际化,宽字符有哪些替代方法?

4

2 回答 2

115

wchar_t 是什么?

wchar_t 的定义使得任何语言环境的 char 编码都可以转换为 wchar_t 表示,其中每个 wchar_t 恰好代表一个代码点:

wchar_t 类型是一种不同类型,其值可以表示支持的语言环境 (22.3.1) 中指定的最大扩展字符集的所有成员的不同代码。

                                                                               — C++ [basic.fundamental] 3.9.1/5

并不要求 wchar_t 足够大以同时表示来自所有语言环境的任何字符。也就是说,用于 wchar_t 的编码可能因地区而异。这意味着您不一定要使用一种语言环境将字符串转换为 wchar_t,然后再使用另一种语言环境转换回 char。1

由于使用 wchar_t 作为所有语言环境之间的通用表示似乎是 wchar_t 在实践中的主要用途,您可能想知道如果不是这样,它有什么用处。

wchar_t 的最初意图和目的是通过定义它来简化文本处理,以便它需要从字符串的代码单元到文本字符的一对一映射,从而允许使用与所使用的相同的简单算法与 ascii 字符串一起使用其他语言。

不幸的是,wchar_t 规范的措辞假设字符和代码点之间的一对一映射来实现这一点。Unicode 打破了假设2,因此您也不能安全地将 wchar_t 用于简单的文本算法。

这意味着可移植软件既不能使用 wchar_t 作为语言环境之间文本的通用表示,也不能使用简单的文本算法。

wchar_t 今天有什么用?

不多,反正对于可移植的代码。如果__STDC_ISO_10646__已定义,则 wchar_t 的值直接表示在所有语言环境中具有相同值的 Unicode 代码点。这样就可以安全地进行前面提到的跨语言环境转换。但是,您不能仅依靠它来决定可以以这种方式使用 wchar_t,因为尽管大多数 unix 平台都定义了它,但即使 Windows 在所有语言环境中使用相同的 wchar_t 语言环境,Windows 也不会。

Windows 没有定义的原因__STDC_ISO_10646__是因为 Windows 使用 UTF-16 作为其 wchar_t 编码,并且因为 UTF-16 使用代理对来表示大于 U+FFFF 的代码点,这意味着 UTF-16 不满足__STDC_ISO_10646__.

对于特定于平台的代码 wchar_t 可能更有用。它在 Windows 上本质上是必需的(例如,某些文件根本无法在不使用 wchar_t 文件名的情况下打开),尽管据我所知,Windows 是唯一正确的平台(所以也许我们可以将 wchar_t 视为“Windows_char_t”)。

事后看来, wchar_t 显然对于简化文本处理或作为与区域设置无关的文本的存储没有用。可移植代码不应尝试将其用于这些目的。非可移植代码可能会发现它很有用,因为某些 API 需要它。

备择方案

我喜欢的替代方法是使用 UTF-8 编码的 C 字符串,即使在对 UTF-8 不是特别友好的平台上也是如此。

通过这种方式,人们可以使用跨平台的通用文本表示来编写可移植代码,将标准数据类型用于预期目的,获得语言对这些类型的支持(例如字符串文字,尽管需要一些技巧才能使其适用于某些编译器),一些标准库支持,调试器支持(可能需要更多技巧)等。对于宽字符,通常更难或不可能获得所有这些,并且您可能会在不同平台上获得不同的部分。

UTF-8 不提供的一件事是能够使用简单的文本算法,例如 ASCII 可能。在这种情况下,UTF-8 并不比任何其他 Unicode 编码差。事实上,它可能被认为更好,因为 UTF-8 中的多代码单元表示更为常见,因此与尝试坚持使用 UTF 相比,处理这种可变宽度字符表示的代码中的错误更容易被注意到和修复-32 带 NFC 或 NFKC。

许多平台使用 UTF-8 作为其原生字符编码,并且许多程序不需要任何重要的文本处理,因此在这些平台上编写国际化程序与不考虑国际化编写代码几乎没有区别。编写更广泛可移植的代码,或在其他平台上编写需要在使用其他编码的 API 的边界处插入转换。

某些软件使用的另一种替代方法是选择跨平台表示,例如保存 UTF-16 数据的无符号短数组,然后提供所有库支持并简单地承受语言支持的成本等。

C++11 添加了新类型的宽字符作为 wchar_t、char16_t 和 char32_t 的替代品,并具有伴随的语言/库功能。这些实际上并不能保证是 UTF-16 和 UTF-32,但我不认为任何主要的实现会使用其他任何东西。C++11 还改进了对 UTF-8 的支持,例如使用 UTF-8 字符串文字,因此没有必要欺骗 VC++ 生成 UTF-8 编码的字符串(尽管我可能会继续这样做而不是使用u8前缀) .

避免的替代方案

TCHAR:TCHAR 用于将采用传统编码的古老 Windows 程序从 char 迁移到 wchar_t,除非您的程序是在上个千年编写的,否则最好忘记它。它不是可移植的,并且在其编码甚至数据类型方面本质上是不确定的,这使得它无法与任何基于非 TCHAR 的 API 一起使用。由于它的目的是迁移到 wchar_t,我们在上面已经看到这不是一个好主意,所以使用 TCHAR 没有任何价值。


1. 可以在 wchar_t 字符串中表示但在任何语言环境中不受支持的字符不需要用单个 wchar_t 值表示。这意味着 wchar_t 可以对某些字符使用可变宽度编码,这显然违反了 wchar_t 的意图。尽管有争议的是 wchar_t 可以表示的字符足以说明语言环境“支持”该字符,但在这种情况下,可变宽度编码是不合法的,并且 Window 对 UTF-16 的使用不符合标准。

2. Unicode 允许用多个代码点表示许多字符,这对简单的文本算法产生了与可变宽度编码相同的问题。即使严格维护组合规范化,某些字符仍然需要多个代码点。见:http ://www.unicode.org/standard/where/

于 2012-06-19T19:05:30.863 回答
21

wchar_t 没有任何“错误”。问题在于,早在 NT 3.x 时代,Microsoft 就认为 Unicode 很好(确实如此),并将 Unicode 实现为 16 位 wchar_t 字符。因此,90 年代中期的大多数 Microsoft 文献几乎等同于 Unicode == utf16 == wchar_t。

可悲的是,事实并非如此。在所有平台上,在所有情况下,“宽字符”不一定是 2 个字节。

这是我见过的关于“Unicode”的最好的入门书之一(独立于这个问题,独立于 C++):我强烈推荐它:

老实说,我相信处理“8 位 ASCII”、“Win32 宽字符”和“wchar_t-in-general”的最佳方法就是接受“Windows 与众不同”……并相应地编码。

恕我直言...

PS:

我完全同意上面的jamesdlin:

在 Windows 上,您真的别无选择。它的内部 API 是为 UCS-2 设计的,这在当时是合理的,因为它在可变长度 UTF-8 和 UTF-16 编码标准化之前。但是现在他们支持 UTF-16,他们最终得到了两全其美的结果。

于 2012-06-25T21:52:32.737 回答