c++ - 如何在标准 C++ 字符串中使用 3 和 4 字节的 Unicode 字符?

标签 c++ string stl stdstring unicode-string

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

我在维基百科中阅读了 the article for UTF-8,我了解到一些 Unicode 字符最多占用 4 个字节的空间。例如,汉字 𤭢 有一个 Unicode 代码点 0x24B62 0x251819211341143 占用内存空间

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

你能添加一个小例子吗?

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

最佳答案

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

概念

与入门编程类(class)中教授的非常简单的文本处理所需的概念相比,您需要一组更细致入微的概念。

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

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

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

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

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

    用户感知的字符就是它们的声音;用户认为他使用的任何语言系统中的字符。

    在过去,char代表所有这些事情:一char是通过定义一个字节,在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() 计算代码单元,如果您需要不同的计数,则必须使用另一种方法。

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

    答案

    今天 charwchar_t 只能被认为是代码单元。 char 只有一个字节这一事实并不能阻止它表示需要两个、三个或四个字节的代码点。您只需按顺序使用两个、三个或四个 char 即可。这就是 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() 是非标准的,所以依赖它是不可移植的。

    关于c++ - 如何在标准 C++ 字符串中使用 3 和 4 字节的 Unicode 字符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12643580/

    相关文章:

    PHP - 字符串逻辑解析 - "X AND Y OR Z"

    c++ - 减少STL vector 的容量

    c++ - 如果您已经包含使用 namespace 标准,为什么不必#include<vector>?

    c++ - =delete 用于用户定义的成员函数,除了构造函数、赋值运算符 c++11

    java - 扫描数据并将其添加到具有未指定分隔符的数组

    java - String.split() --- 如何将连续的分隔符视为一个分隔符?

    c++ - 为什么我们不能像在 C++ 中输入数组那样输入 vector ?

    c++ - 共享库导致段错误

    c++ - _M_X64 是否可以在 x64 处理器上取消设置?

    c++ - std::shared_ptr 和 std::experimental::atomic_shared_ptr 有什么区别?