在C++中,何时使用WCHAR和CHAR?

20

我有一个问题:

一些库使用WCHAR作为文本参数,而另一些库使用CHAR(作为UTF-8):当我编写自己的库时,我需要知道何时使用WCHAR或CHAR。


WCHAR代表宽字符,通常用于处理UNICODE编码风格的文本,据我所知。 - sumitb.mdi
C++ 中没有 WCHAR。你是指 Windows 头文件定义的 WCHAR 宏吗? - David Heffernan
@DavidHeffernan:我认为他指的是Win32头文件中的WCHAR(事实上,我正在考虑编辑OP的标签添加[winapi] :) - Mr.C64
WCHAR并不总是Unicode - 当处理Shift-JIS和BIG5等字符集时,它可能是DBCS。 - cup
6个回答

28

使用char并将其视为UTF-8。有很多原因,这个网站比我更好地总结了它:

http://utf8everywhere.org/

它建议在从任何库接收到wchar_t后尽快转换为char(UTF-16转为UTF-8),并在需要传递字符串时进行转换。所以回答你的问题,除了在API要求你传递或接收wchar_t的时候外,始终使用char


5
实际上,它建议在应用程序不专注于文本时使用UTF-8。我倾向于几乎在任何地方都使用UTF-8,但是例如在编辑器中使用它可能不合适。例如,如果使用UTF-8,则正则表达式等内容将明显变慢。 - James Kanze
@BenHymers 你是想说UTF-8需要8位来编码任何UNICODE代码点吗?例如,代码点范围U+10000-U+10FFFF,UTF-8表示需要4个字节来编码一个代码点。当你说把char作为UTF-8处理时,我不知道它的意思。char怎么能存储UTF-8编码呢? - overexchange
@overexchange 不,我绝对没有这么说,我认为没有人会接受这样的答案 :) 我们谈论的是字符串而不是单个字符 - 所以是一个char数组而不是单个char - 我只是为了简洁省略了“数组”,因为在这种情况下它是暗示的。每个char是UTF-8符号的一个字节,可能是一个或多个字节。 - Ben Hymers
@BenHymers 对于知道如何简单地使用 char*,为什么要引入 wchar,而 wchar 又不具备可移植性? - overexchange
@overexchange 你在做什么?你是在恶意挑衅我吗?你问的问题非常基础,然后又引用详细的文档。我一直很耐心,但现在看起来你只是被动地试图让自己比我聪明,而不是真正寻求帮助。你提供的GNU文档是关于GNU C特定的,其中wchar_t是32位 - 这不是标准的,在其他平台和编译器上不能依赖它。我不会再回复了 - 显然我正在浪费时间,因为你已经深入文档中了。祝你好运。 - Ben Hymers
显示剩余8条评论

11

WCHAR(或Visual C++编译器中的wchar_t)用于Unicode UTF-16字符串。
这是Win32 API使用的“本地”字符串编码。

CHAR(或char)可用于多种其他字符串格式:ANSI、MBCS、UTF-8。

由于UTF-16是Win32 API的本机编码,因此您可能希望在应用程序内部使用WCHAR(最好是基于它的适当的字符串类,如std::wstring)在Win32 API边界处。

而且,您可以使用UTF-8(因此,CHAR/charstd::string)来交换应用程序边界外的Unicode文本。例如:UTF-8在互联网上广泛使用,并且在不同平台之间交换UTF-8文本时,您不必考虑字节序问题(与UTF-16相反,您必须考虑UTF-16BE 大端和UTF-16LE 小端情况)。

您可以使用WideCharToMultiByte()MultiByteToWideChar() Win32 API在UTF-16和UTF-8之间进行转换。这些是纯C APIs,可以方便地在C++代码中进行封装,使用字符串类而不是原始字符指针,以及异常而不是原始错误代码。您可以在此处找到一个示例。


1
@Mgetz:我知道。实际上,我假设OP指的是Win32 SDK头文件中定义的WCHAR,并且他的问题是关于Win32环境的。请注意,我写道:“WCHAR(或Visual C++编译器上的wchar_t)”。 - Mr.C64
1
@Mr.C64 这似乎是一个普遍的假设,但我不会这么认为,因为OP没有指定编译器。 - Mgetz
1
你确定在Windows中使用的是UTF-16而不是UCS-2吗? - AlexDan
2
@BrunoFerreira 实际上,wchar_t 不一定比 char 更宽。唯一的要求是 wchar_t 要足够大,以存储实现支持的最大字符集中每个成员的唯一值。因此,如果实现的最大字符集小于 256,则 wchar_t 可以为 8 位。 - bames53
@AlexDan:如果你回溯到很久以前(我想是NT4),那么它就是UCS-2,但自那以后它一直是UTF-16。 - RichieHindle
显示剩余3条评论

4
正确的问题不是使用哪种类型,而应该是与您的库用户达成什么样的契约。char和wchar_t都可以有多个意义。
对我来说正确的答案是使用char并考虑一切都是utf-8编码,就像utf8everywhere.org建议的那样。这也将使编写跨平台库更容易。
但务必正确使用字符串。某些API(如fopen())会接受char*字符串,并在Windows上编译时处理方式不同(不作为UTF-8)。如果Unicode对您很重要(当您处理字符串时可能很重要),请确保正确处理字符串。 boost::locale有一个很好的示例。我还建议在Windows上使用boost::nowide来在库内正确处理字符串。

2
在Windows中,我们坚持使用WCHARS和std :: wstring。主要是因为如果不这样做,就需要在调用Windows函数时进行转换。
我有一种感觉,仅仅因为http://utf8everywhere.org/而尝试在内部使用utf8,以后可能会给我们带来麻烦。

0
我主要会提到C,但这些也适用于C++,它是一种独立的语言(它不是C的超集!它只是与C相似并部分兼容),但最初是作为C的扩展而开始的,并且受到C的很大影响。
C标准对编码一无所知。在C中,字符串过去是一个字节序列,假设每个字符都适合一个字节,因此您最多可以有255个不同的字符,因为字符0被保留为字符串结束标记。这对于ASCII和西方编码(例如ISO Latin-x编码,Windows-1252等)来说是可以的。C不会以任何方式尝试解释这些字符,它只是一串字节,由系统决定如何解释这些字节。
但是,当C变得广泛应用于国际化时,出现了一个问题:像日语、印度语、韩语、中文、越南语这样的语言有超过255个不同的字符。这些语言也存在编码,但这些编码每个字符使用2个字节。因此,引入了宽字符,它们至少有2个字节长(但也可以更长),可以用来表示具有16位编码的字符。与以前一样,C本身对编码一无所知,也不关心字符是如何编码的。
快进到今天:与此同时,几乎所有经典编码都已经过时,因为我们现在有了Unicode。Unicode是一个字符集,可以容纳大约一百万个字符(如果你想要准确的数字,是1,114,112),足以编码地球上目前使用的所有语言的字符。但是,Unicode只是一个巨大的字符表,将数字映射到字符,如何将这些数字编码为字节是另一个问题。使用的编码取决于实现者,但为了在不同的实现之间提供互操作性和稳定的文件存储格式,定义了3种标准编码:UTF-8、UTF-16和UTF-32(UTF代表Unicode转换格式)。
在UTF-32中,每个字符都表示为32位值。C语言没有对应的表示方法,所以在这里我们忽略这种编码(尽管宽字符可以每个字符占用32位,但标准并没有禁止这种情况,如果你曾经使用过RGB,你就知道使用RGBA更容易)。
当然,32位有些过头了,24位(3字节)就足够了,但是计算机和程序员不喜欢3字节的值(如果你曾经使用过RGB,你就知道使用RGBA更容易)。
在UTF-16中,每个字符由2个或4个字节表示。最重要的字符以16位值呈现,并且可以适应单个16位宽字符。然而,16位只能容纳65536个字符,不足以表示超过100万个可用字符。因此,为了表示不常见的字符,使用了一个技巧:使用两个UTF-16字符来表示字符,称为代理字符。代理字符本身不定义任何字符,它们总是成对出现,每个代理字符对编码一个基本平面之外的单个字符。Unicode将字符按照16个平面进行排序。其中大部分平面未使用,所有广泛使用语言的重要字符都在第一个平面中,即基本平面。在正常的现代文本中,你几乎不会遇到代理字符,除了一个例外:表情符号和象形文字(例如箭头、标记等)位于平面1(补充多语言平面)。此外,一些非常不常见的字符(如部落语言)和历史语言(古印度语、古阿拉伯语、古希腊语等)的字符也可以在那里找到。
旁注:在接下来的回复中,我将忽略一个问题,即字节序。UTF16可以是小端(UTF-16LE)或大端(UTF-16BE),但这只是一个实现细节,取决于您的平台和可能的CPU。Unicode库始终可以处理两者,对于文本文件,可以通过在文件开头放置一个BOM字符来检测字节序。这也可以用于检测是否为UTF-16,还是UTF-8或UTF-32。
今天最重要的编码是UTF-8。在UTF-8中,一个字符可以由1到4个字节表示。最常见的字符使用1个字节(例如英语)或2个字节(大多数西方语言,阿拉伯语,西里尔语等),除了亚洲语言,通常每个字符需要3个字节。只有当您需要超出基本平面时才需要4个字节。UTF-8非常受欢迎,因为它具有“内置”压缩功能(代码中的大多数C字符串只是ASCII字符串,每个字符只使用一个字节,而UTF-16始终使用2个字节,需要两倍的RAM),只要您的代码不对字符串执行任何操作,UTF-8就可以与任何专为8位字符设计的C API很好地配合使用。
你只需要在做像切割、分割或修剪字符串这样的操作时要小心,因为你不能假设每个charwchar_t实际上都代表一个单独的字符。如果你在字符串的任何地方切割,可能会在一个字符的中间切割(例如,如果你在UTF-16中的两个代理字符之间切割,或者在UTF-8中的多字节字符的任何位置切割)。而且更复杂的是,即使你在代码中正确理解了UTF-8/16编码,你仍然无法正确地切割/分割/修剪,因为一个字符可以由多个字符组成。
例如,德语的Umlaut ö可以是Unicode字符0xF6(UTF-16 0x00 0xF6,UTF-8 0xC3 0xB6),也可以由o(Unicode字符0x6F,UTF-16 0x00 0x6F,UTF-8 0x6F)和¨(Unicode字符0xA8,UTF-16 0x00 0xA8,UTF-8 0xC2 0xA8)组成。在屏幕上,它们都看起来像ö,并且表示相同的字形。字符串中的Unicode字符与由该字符串表示的字形之间没有一对一的映射。一个单独的字形可以由多个Unicode字符组成,这些字符也可以超过两个(多个所谓的“修饰符符号”可以叠加在一起)。因此,即使按字节比较两个字符串,也可能导致不正确的结果,因为尽管字节序列可能不同,但这些字符串仍然可能表示相同的一组字形,并且用户期望它们相等,因为它们在屏幕上看起来是相等的。
Unicode非常复杂,一旦你想处理Unicode字符串,就需要使用Unicode库或操作系统提供的Unicode功能。不要试图自己操作Unicode字符串,这是失败的前兆!
至于使用什么:如果你的代码要跨平台,可能就不能依赖任何编码,即使你使用了wchar_t,你处理的可能不是Unicode字符。在跨平台代码中,你无法知道你正在处理的编码,你只能读取和写入字符串,并在API调用之间传递字符串,而不尝试解释它们。你将需要使用一个跨平台的Unicode库来处理Unicode字符或操作Unicode字符串的跨平台C++代码。
如果你只开发特定平台,就使用该平台的本地编码。Windows和Java已经采用了UTF-16,所以在Windows上你会使用wchar_t,因为那是本地系统编码。UNIX/Linux(包括macOS和iOS)更喜欢UTF-8,所以在这些平台上你会使用char。归根结底,你总是可以在需要时在UTF-8和UTF-16之间进行转换,但这些转换是昂贵的,如果能避免就应该避免。
请注意,在Windows 95和NT 3.1之前,Windows使用的是UC-2编码,它只能表示Unicode的一个子集(仅65535个字符),因此每个字符始终是16位。UC-2基本上是没有代理对的UTF-16,并且仅限于基本平面中的字符。
另请注意,虽然大多数Linux发行版今天默认使用UTF-8,但仍然可以设置不同的字符编码,因此您不能依赖于在Linux上处理UTF-8字符的事实。Linux系统API调用不期望任何特定的编码,它们期望字符串根据当前设置的区域设置进行编码,该区域设置还设置了字符集,如果它们需要其他内容,它们将在内部转换字符。

0

在开发 Windows 应用程序时,建议使用 TCHARs。TCHARs 的好处是它们可以是常规字符或宽字符,具体取决于是否设置了 Unicode 设置。一旦您使用 TCHARs,确保您使用的所有字符串操作也都以 _t 前缀开头(例如,_tcslen 表示字符串长度)。这样,您就可以确保您的代码在 Unicode 和 ASCII 环境中都能正常工作。


4
TCHAR 和能够在 charwchar_t 之间切换的能力对于将遗留编码的程序从遗留编码的 char 迁移到 wchar_t 是有用的。 TCHAR 不应用于任何其他目的。 不应使用 TCHAR 编写新软件:新的 Windows 代码应明确使用 (UTF-8 编码的) charwchar_t - bames53
2
关于 TCHAR 的真正糟糕之处在于它既可以是 char 也可以是 wchar_t,因为你必须编写截然不同的代码,具体取决于你使用哪个。无论你选择什么(坦白地说,除非你正在进行文本处理,否则应该选择 char),都要使用它,而不是 TCHAR - James Kanze
1
@armanali 哪种编码格式?你必须处理接收到的任何编码格式。如果是UTF-8,则编写处理UTF-8的代码;如果是UTF-16(BE或LE),则编写处理UTF-16的代码;如果是UTF-32(BE或LE),则编写处理UTF-32的代码。 - James Kanze
1
我认为在库中使用与设置相关的类型(如依赖于UNICODE定义的TCHAR)非常不好。这个问题是关于一个库的。我赞同utf8everywhere.org的观点。 - Pavel Radzivilovsky

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