【问题标题】:How do I use 3 and 4-byte Unicode characters with standard C++ strings?如何在标准 C++ 字符串中使用 3 字节和 4 字节 Unicode 字符?
【发布时间】:2012-09-20 13:11:06
【问题描述】:

在标准 C++ 中,我们有 charwchar_t 用于存储字符。 char 可以存储 0x000xFF 之间的值。而wchar_t 可以存储0x00000xFFFF 之间的值。 std::string 使用 char,所以它只能存储 1 个字节的字符。 std::wstring 使用 wchar_t,因此它可以存储最多 2 字节宽度的字符。这就是我对 C++ 中字符串的了解。到目前为止,如果我说错了,请纠正我。

我在 Wikipedia 中阅读了 the article for UTF-8,了解到某些 Unicode 字符最多占用 4 字节空间。比如汉字????有一个Unicode码位0x24B62,占用内存3字节空间。

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

你能添加一个小例子吗?

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

【问题讨论】:

  • 什么操作系统?有些比其他的要好得多...

标签: c++ string stl stdstring unicode-string


【解决方案1】:

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 数据”视为分开的东西。您可以使用您认为最方便的任何表示,直到您需要(例如)将文本呈现到屏幕上。此时,您需要将其(重新)编码为您的输出目标可以理解的编码。

【讨论】:

  • "(总是?)" 不。例如,AIX 使用 16 位 wchar_t 和 UTF-16。
  • 一个很好的例子是使用 8 位宽 wchar_t 类型的原始 Andriod NDK。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案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 在内存中表示文本。

    【讨论】:

    • 你用什么来表示一个字素簇的序列? ;-p
    • @SteveJessop:是的,字形(难以拼写的单词)集群实际上将多个字形放在同一个位置,并且是一个窗格。
    • 对于任何困惑的人来说——问题是 unicode 文本的完全通用处理,例如“反转字符串”或“仅打印前 10 个字符”的能力实际上需要处理 UTF -32 类似于变长编码。
    【解决方案4】:

    首先,您需要更好地了解 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() 对代码单元进行计数,如果您需要不同的计数,则必须使用其他方法。

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

    答案

    今天charwchar_t 只能被视为代码单元。 char 只有一个字节这一事实并不能阻止它表示占用两个、三个或四个字节的代码点。您只需依次使用两个、三个或四个chars。这就是 UTF-8 的工作方式。同样,使用两个字节 wchar_t 表示 UTF-16 的平台在必要时只需连续使用两个 wchar_tcharwchar_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 argcargv 参数。请参阅GetCommandLineW()CommandLineToArgvW

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

    【讨论】:

      【解决方案5】:

      charwchar_t 不是用于文本字符串的唯一数据类型。 C++11 引入了新的char16_tchar32_t 数据类型以及std::u16stringstd::u32string 各自的STL 类型定义std::basic_string,以解决wchar_t 类型的模糊性,它在不同的服务器上具有不同的大小和编码平台。 wchar_t 在某些平台上是 16 位的,适合 UTF-16 编码,但在其他平台上是 32 位,适合 UTF-32 编码。在所有平台上,char16_t 专门用于 16 位和 UTF-16,char32_t 专门用于 32 位和 UTF-32。

      【讨论】:

        猜你喜欢
        • 2021-03-30
        • 1970-01-01
        • 1970-01-01
        • 2017-06-07
        • 2012-11-30
        • 1970-01-01
        • 2012-04-21
        • 1970-01-01
        相关资源
        最近更新 更多