wchar_t可以表示什么内容?

24
根据cppreference.com上的文档: 是用于宽字符表示的类型(参见宽字符串)。需要足够大以表示任何支持的字符代码点(在支持Unicode的系统上为32位。一个显著的例外是Windows,其中wchar_t为16位并保存UTF-16代码单元)。它具有与整数类型之一相同的大小、符号和对准方式,但是是不同的类型。
标准在[basic.fundamental]/5中说:
类型是一个独立的类型,其值可以表示所有支持语言环境中最大的扩展字符集的所有成员的不同代码。类型应具有与其他整数类型之一相同的大小、符号和对齐要求,称为其基础类型。类型和表示具有与中的和相同大小、符号和对齐要求的不同类型,称为其基础类型。
所以,如果我想处理Unicode字符,我应该使用吗? 等价地,我如何知道特定的Unicode字符是否被支持?

9
在C++中,Unicode支持本质上已经破损了(例如没有区分字符、码点和字形等)。在获得u8char_t之前请使用char来处理UTF-8,并使用一个好的Unicode库,如ICU(记住:到处都要用UTF-8)。 - hlt
我同意hlt的观点,如果你想要处理Unicode,那就使用一个库吧。 - NathanOliver
3
在Windows环境下工作,或者没有跨平台问题时,我会在所有地方使用wchar_t,因为99%的(大量的)Windows API都使用它,即使有些旧API还保留了ANSI支持以确保向后兼容。注意名称可能有所不同,例如LPWSTR等宏,但二进制布局确实是wchar_t。 - Simon Mourier
1
@PaulSanders - C++ 作为一种语言,不支持代理对。这就是为什么“在支持的区域设置中”使 VC++ 通过将“支持的区域设置”限制为那些单个 wchar_t 足够的区域设置而正式符合语言标准。然后 作为扩展,您可以使用更多带有代理对的区域设置,但这不受 C++ 标准支持。 - Bo Persson
1
@BoPersson 在Windows上,_没有_符合标准的语言环境,因为像表情符号这样的东西需要代理对。标准中的声明是含糊不清的,需要修订。 - Paul Sanders
显示剩余4条评论
5个回答

14
所以,如果我想处理Unicode字符,我应该使用wchar_t吗?
首先,请注意,编码并不强制您使用特定类型来表示某个字符。您可以像wchar_t一样使用char来表示Unicode字符 - 您只需要记住,根据UTF-8、UTF-16或UTF-32编码,最多4个char一起将形成一个有效代码点,而wchar_t可以使用1个(在Linux等上使用UTF-32)或最多2个一起工作(在Windows上使用UTF-16)。
接下来,没有确定的Unicode编码。一些Unicode编码使用固定宽度来表示代码点(如UTF-32),而其他编码(如UTF-8和UTF-16)具有可变长度(例如字母'a'肯定只会使用1个字节,但除了英文字母外,其他字符肯定会使用更多字节进行表示)。
因此,您必须决定要表示哪种字符,然后相应地选择编码方式。根据您想要表示的字符类型,这将影响数据所使用的字节数量。例如,使用UTF-32来表示大部分英文字符将导致许多0字节。UTF-8是许多拉丁语言的更好选择,而UTF-16通常是东亚语言的更好选择。

一旦您做出决定,您应该最小化转换量并保持决策的一致性。

在下一步中,您可以决定适合表示数据的数据类型(或需要进行哪种类型的转换)。

如果您想要基于代码点进行文本操作/解释,则 char 肯定不是一个好选择,特别是当您拥有例如日语汉字时。但是,如果您只是想要传达数据并且将其视为不再是字节的数量序列,那么您可以选择使用 char

UTF-8 everywhere 的链接已经作为评论发布了,我建议您也去看一下。另一篇好文章是What every programmer should know about encodings

到目前为止,在C++中仅支持Unicode的基本语言支持(例如 char16_tchar32_t 数据类型以及 u8/u/U 字面前缀)。因此,选择用于管理编码(特别是转换)的库肯定是一个好建议。


您好,我可以问一个问题吗?关于“...决定什么数据类型...不再将其视为字节的数量序列...”,如果我想处理UTF-8编码的字符(而不是字节)序列,就像ASCII char/string一样(参考),我使用像UTF8-CPP-> uint32_t utf8::next(...);这样的库来拆分std::string,那么每个结果中的每个“项”都需要占用32位吗?这种浪费在内存中是不可避免的吗?或者只有使用可变长度的标准才有意义,当谈论磁盘存储时才会出现? - Saddle Point
1
@Edityouprofile "内存中的这种浪费是不可避免的吗?" 当您只想处理数据时,内存并不是问题。对于处理代码点,您选择了足够大的类型进行表示。如果存储数据(如果有的话),则大小考虑更为相关。 - Jodocus

11

wchar_t在Windows中使用UTF16-LE格式。 wchar_t需要宽字符函数,例如wcslen(const wchar_t*)而不是strlen(const char*)std::wstring而不是std::string

基于Unix的机器(Linux,Mac等)使用UTF8。这使用char进行存储,并且对于ASCII使用相同的C和C++函数,例如strlen(const char*)std::string(有关std::find_first_of的注释见下文)

wchar_t在Windows中是2字节(UTF16)。但在其他机器上是4字节(UTF32)。这使事情更加混乱。

对于UTF32,可以使用std::u32string在不同系统上都一样。


您可能考虑将UTF8转换为UTF32,因为这样每个字符始终为4个字节,并且您可能认为字符串操作会更容易。 但那很少必要。

UTF8被设计为使0到128之间的ASCII字符不用于表示其他Unicode代码点。 这包括转义序列'\'printf格式说明符以及常用解析字符,例如,

考虑以下UTF8字符串。假设您想要找到逗号

std::string str = u8"汉,"; //3 code points represented by 8 bytes

逗号的ASCII值为44,并且str保证只包含一个字节,其值为44。要查找逗号,可以使用C或C++中的任何标准函数来查找','

要查找,可以搜索字符串u8"汉",因为此代码点无法表示为单个字符。

一些C和C++函数在处理UTF8时不太流畅。这些函数包括

strtok
strspn
std::find_first_of

以上函数的参数是一组字符,而不是实际的字符串。

因此,str.find_first_of(u8"汉")不能工作。因为u8"汉"占用3个字节,而find_first_of将查找这些字节中的任意一个。有可能其中一个字节被用来表示另一个码点。

另一方面,str.find_first_of(u8",;abcd")是安全的,因为搜索参数中的所有字符都是ASCII字符(str本身可以包含任何Unicode字符)

在罕见的情况下可能需要使用UTF32(尽管我无法想象在哪里需要!)。 您可以使用std::codecvt将UTF8转换为UTF32以运行以下操作:

std::u32string u32 = U"012汉"; //4 code points, represented by 4 elements
cout << u32.find_first_of(U"汉") << endl; //outputs 3
cout << u32.find_first_of(U'汉') << endl; //outputs 3

顺便提一下:

您应该“到处使用Unicode”,而不是“到处使用UTF8”

在Linux、Mac等系统中使用UTF8表示Unicode。

在Windows系统中,使用UTF16表示Unicode。Windows程序员使用UTF16,他们不会将其转换为UTF8然后再转回来,这样做没有意义。但是,在Windows中使用UTF8也是有合理情况的。

Windows程序员倾向于使用UTF8保存文件、网页等内容。这对于非Windows程序员来说在兼容性方面就不用太担心了。

编程语言本身并不关心您想使用哪种Unicode格式,但从实用角度考虑,应该选择与您所在的操作系统匹配的格式。



我同意你的“Unicode 无处不在”的附注(尽管“UTF8 无处不在”很有道理,但可能是由那些不是每天都在 Windows 上编码的人编写的)。然而,对于跨平台的“字符串类型”,什么才是完美的选择呢? - Simon Mourier
1
@SimonMourier,我认为在这方面对于C++/Windows没有明显的解决方案,除了尝试保持程序输入/输出与UTF8兼容。一个普通的WinAPI程序无论如何都不兼容任何其他东西,因此字符串类型不兼容并没有得到太多关注。 - Barmak Shemirani

5
那么,如果我想处理Unicode字符,我应该使用wchar_t吗?
这取决于你正在处理的编码方式。对于UTF-8编码方式,使用char和std::string是完全可以胜任的。 UTF-8表示最小编码单位为8位,所有的Unicode代码点从U+0000到U+007F只需用1个字节进行编码。从代码点U+0080开始,UTF-8使用2个字节进行编码,从U+0800开始使用3个字节进行编码,从U+10000开始使用4个字节进行编码。为了处理这种可变宽度(1字节 - 2字节 - 3字节 - 4字节)的字符,char适合得最好。注意,像strlen这样的C函数提供的是基于字节的结果: "öö"实际上是一个由两个字符组成的文本,但strlen将返回4,因为'ö'被编码为0xC3B6。
UTF-16表示最小编码单位为16位:所有的代码点从U+0000到U+FFFF都是由2个字节编码的;从U+100000开始使用4个字节。在使用UTF-16时,应该使用wchar_t和std::wstring,因为你大多数情况下遇到的字符都是由2个字节编码的。在使用wchar_t时,你无法再使用像strlen这样的C函数,你必须使用宽字符等价函数,如wcslen。
当使用Visual Studio并使用“Unicode”配置进行编译时,你将获得UTF-16编码:TCHAR和CString将基于wchar_t而不是char。

4
一切都取决于您所说的“处理”,但有一件事是肯定的:在Unicode方面,std :: basic_string并没有提供任何实际功能。
在任何特定程序中,您都需要执行X个Unicode感知操作,例如智能字符串匹配,大小写折叠,正则表达式,查找单词断点,可能使用Unicode字符串作为路径名等等。
支持这些操作几乎总会有某种库和/或平台提供的本机API,对我来说,目标是以这样的方式存储和操作我的字符串,以便可以在整个代码中不必散布底层库和本机API支持的知识。 我还想未来证明自己在存储字符串中的字符宽度上是正确的,以防我改变主意。
例如,假设您决定使用ICU来完成繁重的工作。 立即出现一个明显的问题: icu :: UnicodeString std :: basic_string 没有任何关系。 该怎么办? 在整个代码中专门使用 icu :: UnicodeString ? 可能不是。
或者,应用程序的重点从欧洲语言转向亚洲语言,因此UTF-16比UTF-8更好(也许)。
因此,我的选择是使用从std :: basic_string 派生的自定义字符串类,类似于此:
typedef wchar_t mychar_t;  // say

class MyString : public std::basic_string <mychar_t>
{
...
};

你可以灵活选择在容器中存储的代码单元的大小。但你可以做更多的事情。例如,使用以上声明(并在其中添加必要的构造函数的样板),你仍然无法这样说:

MyString s = "abcde";

因为“abcde”是一个窄字符串,而各种std::basic_string <wchar_t>的构造函数都期望宽字符串。微软通过宏(TEXT("...")__T("..."))解决了这个问题,但这很麻烦。现在我们只需要在MyString中提供一个适当的构造函数,签名为MyString(const char *s),问题就解决了。
实际上,这个构造函数可能会期望一个UTF-8字符串,无论MyString使用的基础字符宽度是什么,并在必要时进行转换。有人在这里评论说,你应该将字符串存储为UTF-8,这样你就可以从代码中的UTF-8文字面量构造它们。好吧,现在我们已经打破了这个限制。我们字符串的基础字符宽度可以是任何我们喜欢的东西。
这个线程中人们一直在谈论的另一件事是,find_first_of对于UTF-8字符串(以及一些UTF-16字符串)可能无法正常工作。现在,您可以提供一个正确执行此操作的实现。应该只需要大约半小时的时间。如果std::basic_string中还有其他“损坏”的实现(我确信有),则大多数实现可能都可以被类似的方式替换。
至于其他方面,它主要取决于您想在MyString类中实现什么级别的抽象。如果您的应用程序愿意依赖于ICU,那么您只需要提供一些方法来转换为和从icu::UnicodeString。这可能是大多数人会做的事情。
或者,如果您需要将UTF-16字符串传递给/从本机Windows API,则可以添加方法以将其转换为和从const WCHAR *(再次,您会以这样的方式实现它们,以使它们适用于mychar_t的所有值)。或者您可以进一步抽象平台和库所提供的Unicode支持的某些或全部内容。例如,Mac具有丰富的Unicode支持,但它仅从Objective-C中可用,因此您必须进行包装。这取决于您希望代码的可移植性程度。
因此,您可以添加任何您喜欢的功能,可能是在工作进展的过程中,而不会失去将字符串作为std::basic_string的能力。或多或少。只是尽量不要编写假定自己知道宽度或不包含代理对的代码。

3
首先,正如您在问题中指出的那样,您应该检查是否正在使用带有16位wchar_t的Windows和Visual Studio C++,因为在这种情况下,为了使用完整的Unicode支持,您需要假定UTF-16编码。
基本问题不在于您使用的wchar_t的大小,而是您将要使用的库是否支持完全的Unicode支持。
Java也存在类似的问题,因为它的char类型宽度为16位,所以它无法先验地支持完整的Unicode空间,但是它确实支持,因为它使用UTF-16编码和配对代理以处理完整的24位代码点。
值得注意的是,UNICODE仅使用高位平面来编码不常用的代码点,这些代码点通常不会在日常使用中使用。
无论如何,要支持Unicode,您需要使用宽字符集,因此wchar_t是一个很好的开始。如果您将要使用Visual Studio工作,则必须检查其库如何处理Unicode字符。
另一件值得注意的事情是,标准库仅在添加区域设置支持(这需要初始化某个库,例如setlocale(3))时才处理字符集(包括Unicode),因此,在您未调用setlocale(3)的情况下,您将看不到任何Unicode(仅限基本ASCII)。
几乎所有str*(3)函数都有宽字符函数,以及任何stdio.h库函数,以处理wchar_t。深入研究/usr/include/wchar.h文件将揭示这些例程的名称。请参阅手册页面以获取有关它们的文档:fgetws(3),fputwc(3),fputws(3),fwide(3),fwprintf(3)等。
最后,请再次考虑,如果您正在处理Microsoft Visual C ++,则从一开始就有不同的实现。即使它们完全符合标准,您也必须应对具有不同实现的某些怪癖。可能会有不同的函数名称用于某些用途。

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