首先,您需要更好地理解 Unicode。您的问题的具体答案在底部。
概念
您需要一组比入门编程课程中教授的非常简单的文本处理所需的更细微的概念。
字节是内存的最小可寻址单元。现在通常是 8 位,能够存储多达 256 个不同的值。根据定义,一个 char 是一个字节。
代码单元是用于存储文本的最小固定大小的数据单元。当您并不真正关心文本的内容而只想将其复制到某个地方或计算文本使用了多少内存时,您就会关心代码单元。否则代码单元没有多大用处。
代码点表示字符集的不同成员。无论字符集中的“字符”是什么,它们都被分配了一个唯一的编号,每当您看到编码的特定数字时,您就知道您正在处理的是字符集中的哪个成员。
抽象字符是在语言系统中具有意义的实体,并且不同于其表示或分配给该意义的任何代码点。
用户感知的字符就是它们听起来的样子;用户在他使用的任何语言系统中认为的字符。
在过去,它char
表示所有这些东西:achar
定义为一个字节,在char*
字符串中,代码单位是char
s,字符集很小,所以 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
并不能阻止它表示占用两个、三个或四个字节的代码点。您只需char
按顺序使用两个、三个或四个 s。这就是 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编码的数据。
在 Windowsmain()
上,不仅支持 ASCII,还支持任何系统char
编码。不幸的是,Windows 不像char
其他平台那样支持 UTF-8 作为系统编码,因此您只能使用 cp1252 等传统编码或您的系统配置使用的任何编码。但是,您可以使用 Win32 API 调用直接访问 UTF-16 命令行参数,而不是使用main()
sargc
和argv
参数。见GetCommandLineW()
和CommandLineToArgvW
。
wmain()
的argv
参数完全支持Unicode。wchar_t
存储在Windows 中的 16 位代码单元是 UTF-16 代码单元。Windows API 本机使用 UTF-16,因此在 Windows 上使用起来非常容易。wmain()
虽然是非标准的,所以依赖它不会是可移植的。