首先,您需要更好地了解 Unicode。您的问题的具体答案在底部。
概念
与入门编程课程中教授的非常简单的文本处理相比,您需要一组更细微的概念。
字节是内存的最小可寻址单元。现在通常是 8 位,能够存储多达 256 个不同的值。根据定义,一个 char 是一个字节。
代码单元是用于存储文本的最小固定大小的数据单元。当您并不真正关心文本的内容而只想将其复制到某处或计算文本使用了多少内存时,您就会关心代码单元。否则代码单元用处不大。
代码点表示字符集的不同成员。无论字符集中的“字符”是什么,它们都被分配了一个唯一的编号,每当您看到编码的特定数字时,您就知道您正在处理的是字符集中的哪个成员。
抽象字符是在语言系统中具有含义的实体,并且不同于其表示或分配给该含义的任何代码点。
用户感知的字符就是它们听起来的样子;用户在他使用的任何语言系统中认为的字符。
在过去,char 代表所有这些东西:char 定义为一个字节,在char* 字符串中,代码单元是chars,字符集很小,所以 256 个值char 可以代表每个成员,而且支持的语言系统也很简单,所以字符集的成员大多代表了用户想要直接使用的字符。
但是char 代表几乎所有内容的这个简单系统不足以支持更复杂的系统。
遇到的第一个问题是某些语言使用的字符远多于 256 个。因此引入了“宽”字符。宽字符仍然使用单一类型来表示上述四个概念,代码单元、代码点、抽象字符和用户感知字符。然而,宽字符不再是单字节。这被认为是支持大型字符集的最简单方法。
代码可能基本相同,只是它处理的是宽字符而不是char。
然而事实证明,许多语言系统并不是那么简单。在某些系统中,不必让每个用户感知的字符都必须由字符集中的单个抽象字符表示是有意义的。因此,使用 Unicode 字符集的文本有时会使用多个抽象字符来表示用户感知的字符,或者使用单个抽象字符来表示多个用户感知的字符。
宽字符还有另一个问题。由于它们增加了代码单元的大小,它们增加了用于每个字符的空间。如果希望处理可以由单字节代码单元充分表示的文本,但必须使用宽字符系统,那么所使用的内存量将高于单字节代码单元的情况。因此,希望宽字符不要太宽。同时,宽字符需要足够宽,以便为字符集的每个成员提供唯一值。
Unicode 目前包含大约 100,000 个抽象字符。事实证明,这需要比大多数人愿意使用的宽字符。结果是一个宽字符系统;使用大于一个字节的代码单元直接存储代码点值是不可取的。
总而言之,最初不需要区分字节、代码单元、代码点、抽象字符和用户感知字符。然而,随着时间的推移,有必要区分这些概念。
编码
在上述之前,文本数据很容易存储。每个用户感知的字符都对应一个抽象字符,该抽象字符具有代码点值。字符太少了,256 个值就足够了。因此,只需将与所需的用户感知字符相对应的代码点编号直接存储为字节。后来,对于宽字符,对应于用户感知字符的值直接存储为更大尺寸的整数,例如 16 位。
但是由于以这种方式存储 Unicode 文本会使用比人们愿意花费更多的内存(每个字符三个或四个字节)Unicode“编码”存储文本不是直接存储代码点值,而是使用可逆函数计算一些代码单元值以存储每个代码点。
例如,UTF-8 编码可以采用最常用的 Unicode 代码点,并使用单个字节代码单元来表示它们。不太常见的代码点使用两个一字节代码单元存储。使用三个或四个代码单元存储仍然不太常见的代码点。
这意味着通常可以使用 UTF-8 编码存储普通文本,使用比 16 位宽字符方案更少的内存,而且存储的数字不一定直接对应于抽象字符的代码点值。相反,如果您需要知道存储了哪些抽象字符,则必须“解码”存储的代码单元。如果您需要了解用户感知的字符,您必须进一步将抽象字符转换为用户感知的字符。
有许多不同的编码,为了将使用这些编码的数据转换为抽象字符,您必须知道正确的解码方法。如果您不知道使用什么编码将代码点值转换为代码单元,那么存储的值实际上是没有意义的。
编码的一个重要含义是您需要知道对编码数据的特定操作是否有效或有意义。
例如,如果您想获取字符串的“大小”,您是在计算字节数、代码单元、抽象字符还是用户感知的字符? std::string::size() 对代码单元进行计数,如果您需要不同的计数,则必须使用其他方法。
再举一个例子,如果你拆分一个编码字符串,你需要知道你这样做的方式是否使得结果在该编码中仍然有效并且数据的含义没有无意中改变。例如,您可能会在属于同一代码点的代码单元之间进行拆分,从而产生无效的编码。或者您可能会在代码点之间进行拆分,这些代码点必须组合起来以表示用户感知的字符,从而产生用户认为不正确的数据。
答案
今天char 和wchar_t 只能被视为代码单元。 char 只有一个字节这一事实并不能阻止它表示占用两个、三个或四个字节的代码点。您只需依次使用两个、三个或四个chars。这就是 UTF-8 的工作方式。同样,使用两个字节 wchar_t 表示 UTF-16 的平台在必要时只需连续使用两个 wchar_t。 char 和 wchar_t 的实际值并不单独表示 Unicode 代码点。它们表示由编码代码点产生的代码单元值。例如。 Unicode 代码点 U+0400 以 UTF-8 -> 0xD0 0x80 编码为两个代码单元。 Unicode 代码点 U+24B62 同样被编码为四个代码单元0xF0 0xA4 0xAD 0xA2。
因此您可以使用std::string 来保存UTF-8 编码的数据。
在 Windows 上,main() 不仅支持 ASCII,还支持任何系统 char 编码。不幸的是,Windows 不支持 UTF-8 作为系统 char 编码的其他平台的方式,因此您仅限于像 cp1252 或您的系统配置使用的任何传统编码。但是,您可以使用 Win32 API 调用直接访问 UTF-16 命令行参数,而不是使用 main()s argc 和 argv 参数。请参阅GetCommandLineW() 和CommandLineToArgvW。
wmain() 的argv 参数完全支持Unicode。 Windows 上wchar_t 中存储的 16 位代码单元是 UTF-16 代码单元。 Windows API 本机使用 UTF-16,因此在 Windows 上使用起来非常容易。 wmain() 虽然是非标准的,所以依赖它是不可移植的。