13

在标准 C++ 中,我们有charwchar_t用于存储字符。char可以存储0x000xFF之间的值。并且wchar_t可以存储 和 之间0x0000的值0xFFFFstd::string使用char,因此它只能存储 1 个字节的字符。std::wstring使用wchar_t,因此它可以存储最多 2 字节宽度的字符。这就是我对 C++ 中字符串的了解。如果我在这一点上说错了,请纠正我。

我在 Wikipedia 中阅读了有关 UTF-8 的文章,了解到某些 Unicode 字符最多占用 4 字节空间。例如,汉字有一个 Unicode 代码点0x24B62,它占用内存中的 3 字节空间。

是否有用于处理此类字符的 STL 字符串容器?我正在寻找类似的东西std::string32。此外,我们有main()ASCII 入口点,wmain()用于支持 16 位字符的入口点;对于 3 和 4 字节 Unicode 支持的代码,我们使用什么入口点?

你能添加一个小例子吗?

(我的操作系统:Windows 7 x64)

4

5 回答 5

24

首先,您需要更好地理解 Unicode。您的问题的具体答案在底部。

概念

您需要一组比入门编程课程中教授的非常简单的文本处理所需的更细微的概念。

  • 字节
  • 代码单元
  • 代码点
  • 抽象人物
  • 用户感知字符

字节是内存的最小可寻址单元。现在通常是 8 位,能够存储多达 256 个不同的值。根据定义,一个 char 是一个字节。

代码单元是用于存储文本的最小固定大小的数据单元。当您并不真正关心文本的内容而只想将其复制到某个地方或计算文本使用了多少内存时,您就会关心代码单元。否则代码单元没有多大用处。

代码点表示字符集的不同成员。无论字符集中的“字符”是什么,它们都被分配了一个唯一的编号,每当您看到编码的特定数字时,您就知道您正在处理的是字符集中的哪个成员。

抽象字符是在语言系统中具有意义的实体,并且不同于其表示或分配给该意义的任何代码点。

用户感知的字符就是它们听起来的样子;用户在他使用的任何语言系统中认为的字符。

在过去,它char表示所有这些东西:achar定义为一个字节,在char*字符串中,代码单位是chars,字符集很小,所以 256 个值char可以表示每个成员,而语言系统是支持的很简单,所以字符集的成员大多代表了用户想要直接使用的字符。

但是这个char代表几乎所有东西的简单系统不足以支持更复杂的系统。


遇到的第一个问题是某些语言使用的字符远多于 256 个字符。因此引入了“宽”字符。宽字符仍然使用单一类型来表示上述四个概念,代码单元、代码点、抽象字符和用户感知字符。然而,宽字符不再是单字节。这被认为是支持大型字符集的最简单方法。

代码可能大体相同,只是它会处理宽字符而不是char.

然而事实证明,许多语言系统并不是那么简单。在某些系统中,不必让每个用户感知的字符都必须由字符集中的单个抽象字符表示是有意义的。因此,使用 Unicode 字符集的文本有时使用多个抽象字符来表示用户感知的字符,或者使用单个抽象字符来表示多个用户感知的字符。

宽字符还有另一个问题。由于它们增加了代码单元的大小,它们增加了用于每个字符的空间。如果希望处理可以由单字节代码单元充分表示但必须使用宽字符系统的文本,那么所使用的内存量将高于单字节代码单元的情况。因此,希望宽字符不要太宽。同时,宽字符需要足够宽,以便为字符集的每个成员提供唯一值。

Unicode 目前包含大约 100,000 个抽象字符。事实证明,这需要比大多数人愿意使用的宽字符。结果是一个宽字符系统;使用大于一个字节的代码单元来直接存储代码点值是不可取的。

综上所述,最初不需要区分字节、代码单元、代码点、抽象字符和用户感知字符。然而,随着时间的推移,有必要区分这些概念中的每一个。


编码

在上述之前,文本数据很容易存储。每个用户感知的字符都对应一个抽象字符,该抽象​​字符具有代码点值。字符太少了,256 个值就足够了。因此,只需将与所需的用户感知字符相对应的代码点编号直接存储为字节。后来,对于宽字符,对应于用户感知字符的值直接存储为更大尺寸的整数,例如 16 位。

但是由于以这种方式存储 Unicode 文本会使用比人们愿意花费更多的内存(每个字符三个或四个字节)Unicode“编码”存储文本不是通过直接存储代码点值,而是通过使用可逆函数来计算一些为每个代码点存储的代码单元值的数量。

例如,UTF-8 编码可以采用最常用的 Unicode 代码点,并使用单个字节代码单元来表示它们。不太常见的代码点使用两个一字节代码单元存储。使用三个或四个代码单元存储仍然不太常见的代码点。

这意味着通常可以使用 UTF-8 编码存储普通文本,使用比 16 位宽字符方案更少的内存,而且存储的数字不一定直接对应于抽象字符的代码点值。相反,如果您需要知道存储了哪些抽象字符,则必须“解码”存储的代码单元。如果您需要了解用户感知字符,则必须进一步将抽象字符转换为用户感知字符。

有许多不同的编码,为了使用这些编码将数据转换为抽象字符,您必须知道正确的解码方法。如果您不知道使用什么编码将代码点值转换为代码单元,则存储的值实际上毫无意义。


编码的一个重要含义是您需要知道对编码数据的特定操作是否有效或有意义。

例如,如果您想获取字符串的“大小”,您是在计算字节数、代码单元、抽象字符还是用户感知字符?std::string::size()计算代码单元,如果您需要不同的计数,则必须使用另一种方法。

再举一个例子,如果你拆分一个编码字符串,你需要知道你这样做的方式是否使得结果在该编码中仍然有效并且数据的含义没有无意中改变。例如,您可能会在属于同一代码点的代码单元之间进行拆分,从而产生无效的编码。或者,您可能会在代码点之间进行拆分,这些代码点必须组合起来以表示用户感知的字符,从而产生用户认为不正确的数据。

答案

今天charwchar_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()sargcargv参数。见GetCommandLineW()CommandLineToArgvW

wmain()argv参数完全支持Unicode。wchar_t存储在Windows 中的 16 位代码单元是 UTF-16 代码单元。Windows API 本机使用 UTF-16,因此在 Windows 上使用起来非常容易。wmain()虽然是非标准的,所以依赖它不会是可移植的。

于 2012-09-28T17:59:09.277 回答
4

的大小和含义wchar_t是实现定义的。正如你所说,在 Windows 上它是 16 位,在类 Unix 系统上它通常是 32 位,但并非总是如此。

就此而言,允许编译器做自己的事情并选择wchar_t与系统所说的不同的大小——它只是与系统的其余部分不兼容 ABI。

C++11 提供了std::u32string,用于表示 unicode 代码点的字符串。我相信足够新的微软编译器包括它。由于 Microsoft 的系统函数需要 16 位宽字符(又名 UTF-16le),而不是 32 位 unicode 代码点(又名 UTF-32、UCS-4),因此它的用途有限。

不过,您提到了 UTF-8:UTF-8 编码的数据可以存储在常规的std::string. 当然,由于它是可变长度编码,因此您无法通过索引访问 unicode 代码点,您只能通过索引访问字节。但是您通常会编写代码而不需要通过索引访问代码点,即使使用u32string. Unicode 代码点与可打印字符(“字形”)不对应 1-1,因为在 Unicode 中存在组合标记,所以在学习编程时,您使用字符串玩的许多小技巧(反转它们,搜索子字符串)无论您将 Unicode 数据存储在什么位置,都不会那么轻松地使用它。

正如你所说,这个角色是\u24B62。它被 UTF-8 编码为一系列四个字节,而不是三个:F0 A4 AD A2。在 UTF-8 编码数据和 unicode 代码点之间进行转换是很费力的(诚然,不需要大量的工作,库函数会为您完成)。最好将“编码数据”和“Unicode 数据”视为分开的东西。您可以使用任何您认为最方便的表示,直到您需要(例如)将文本呈现到屏幕上。此时,您需要将其(重新)编码为您的输出目的地可以理解的编码。

于 2012-09-28T16:14:46.477 回答
4

Windows 使用UTF-16。U+0000 到 U+D7FF 和 U+E000 到 U+FFFF 范围内的任何代码点将被直接存储;根据 UTF-16 编码规则,这些范围之外的任何值都将被拆分为两个 16 位值。

例如 0x24B62 将被编码为 0xd892,0xdf62。

您可以按照您喜欢的任何方式转换字符串以使用它们,但 Windows API 仍然需要并提供 UTF-16,因此这可能是最方便的。

于 2012-09-28T16:20:26.000 回答
3

在标准 C++ 中,我们有 char 和 wchar_t 来存储字符?char 可以存储 0x00 和 0xFF 之间的值。wchar_t 可以存储 0x0000 到 0xFFFF 之间的值

不完全的:

sizeof(char)     == 1   so 1 byte per character.
sizeof(wchar_t)  == ?   Depends on your system 
                        (for unix usually 4 for Windows usually 2).

Unicode 字符最多占用 4 字节空间。

不完全的。Unicode 不是一种编码。Unicode 是一个标准,它定义了每个代码点是什么,并且代码点限制为 21 位。前 16 位定义了代码明文上的字符位置,而后 5 位定义了字符所在的明文。

有几种 unicode编码(UTF-8、UTF-16 和 UTF-32 是最常见的)这是您在内存中存储字符的方式。三者之间存在实际差异。

    UTF-8:非常适合存储和运输(因为它很紧凑)
             不好,因为它是可变长度的
    UTF-16:几乎在所有方面都很糟糕
             它总是很大并且是可变长度的
             (不在 BMP 上的任何东西都需要编码为代理对)
    UTF-32:非常适合内存表示,因为它是固定大小的
             不好,因为每个字符需要 4 个字节,这通常是多余的

就我个人而言,我使用 UTF-8 进行传输和存储,使用 UTF-32 在内存中表示文本。

于 2012-09-28T16:26:58.530 回答
1

char并且wchar_t不是用于文本字符串的唯一数据类型。C++11 引入了新的char16_tchar32_t数据类型以及各自的 STLstd::u16stringstd::u32stringtypedef std::basic_string,以解决wchar_t在不同平台上具有不同大小和编码的类型的模糊性。 wchar_t在某些平台上是 16 位,适合 UTF-16 编码,但在其他平台上是 32 位,适合 UTF-32 编码。 char16_t特别是 16 位和 UTF-16,char32_t特别是 32 位和 UTF-32,在所有平台上。

于 2012-10-03T06:37:08.787 回答