在标准C中,我们有char
和wchar_t
来存储字符。char
可以存储 * 0x 00 * 和 0xFF 之间的值。wchar_t
可以存储0x0000
和0xFFFF
之间的值。std::string
使用char
,因此它只能存储1字节字符。std::wstring
使用wchar_t
,所以它可以存储2字节宽的字符。2这是我所知道的C中的字符串。3如果到目前为止我说错了什么,请纠正我。
我在Wikipedia上读了关于UTF-8的文章,了解到一些Unicode字符占用4字节的空间。例如,中文字符𤭢的Unicode码位为0x 24 B62,它占用了3字节的内存空间。
有没有一个STL字符串容器来处理这类字符?我正在寻找类似std::string32
的东西。另外,我们有main()
作为ASCII入口点,wmain()
作为支持16位字符的入口点;对于支持3字节和4字节Unicode代码,我们使用什么入口点?
你能再举个小例子吗?
(My操作系统:Windows 7
5条答案
按热度按时间pbpqsu0x1#
首先你需要更好地理解Unicode。你的问题的具体答案在底部。
概念
与编程入门课程中教授的非常简单的文本处理所需的概念相比,您需要一组更细致的概念。
一个字节是存储器中最小的可寻址单位。目前通常为8位,最多可存储256个不同的值。根据定义,一个字符是一个字节。
代码单元是用于存储文本的最小的固定大小的数据单元。当你并不真正关心文本的内容,而只是想把它复制到某个地方或计算文本使用了多少内存时,你就关心代码单元。否则代码单元就没有多大用处了。
一个代码点代表一个字符集的一个独特的成员。无论字符集中的“字符”是什么,它们都被分配了一个唯一的数字,每当你看到一个特定的数字被编码,你就知道你在处理字符集的哪个成员。
抽象字符是在语言系统中具有含义的实体,并且不同于其表示或分配给该含义的任何代码点。
用户感知的字符是它们听起来像什么;用户在他使用的任何语言系统中认为的字符。
在过去,
char
代表所有这些东西:根据定义,char
是一个字节,在char*
字符串中,代码单元是char
,字符集很小,因此可以由char
表示的256个值足以表示每个成员,并且所支持的语言系统很简单,因此字符集的成员大多表示用户想要直接使用的字符。但是这个简单的系统用
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
。这就是UTF-8的工作原理。同样,使用两个字节wchar_t
来表示UTF-16的平台在必要时只需要在一行中使用两个wchar_t
。char
和wchar_t
的实际值并不单独表示Unicode码位。它们表示通过对码位进行编码而得到的代码单元值。例如:Unicode码位U+0400被编码为UTF-8 -〉0xD0 0x80
的两个码元,Unicode码位U+24 B62同样被编码为0xF0 0xA4 0xAD 0xA2
的四个码元。因此,您可以使用
std::string
来保存UTF-8编码的数据。在Windows上,
main()
不仅支持ASCII,而且支持任何char
系统编码。现在甚至Windows也支持UTF-8作为char
系统编码,并且不再局限于传统编码。不过,您可能需要为此配置Windows;我不确定它是否是默认的,如果不是,希望它成为默认的。您也可以使用Win32 API呼叫来直接存取UTF-16命令列参数,而不使用
main()
、argc
和argv
参数。请参阅GetCommandLineW()
和CommandLineToArgvW
。wmain()
的argv
参数完全支持Unicode。在Windows上,存储在wchar_t
中的16位代码单元是UTF-16代码单元。Windows API本身就使用UTF-16,因此在Windows上使用它非常容易。但是wmain()
是非标准的,因此依赖它是不可移植的。示例:
如果编译器使用UTF-8作为窄执行编码,那么
s
将包含UTF-8数据。如果你用来运行编译程序的终端支持UTF-8,并且配置为使用它,并且使用支持该字符的字体𤭢,那么你应该可以看到该字符被这个程序打印出来。在Windows上,我使用/utf-8标志
cl.exe /EHsc /utf-8 tmp.cpp
进行编译,并运行命令将控制台设置为UTF-8chcp 65001
,结果程序打印出了正确的数据。尽管由于缺少字体支持,字符显示为一个带问号的框。从控制台复制文本并粘贴到适当支持的地方,显示出写入了正确的字符。使用
/utf-8
,您还可以直接以字符串文字形式编写utf-8数据,而不是使用\Uxxxxxxxx
语法。在GCC中,你可以使用标志
-fexec-charset=utf-8
来构建这个程序,尽管它应该是默认的。-finput-charset=utf-8
允许你直接在你的字符串文字中写入UTF-8编码的数据。Clang只支持UTF-8。
dddzy1tm2#
Windows 使用 UTF-16 , U + 0000 到 U + D7FF 和 U + E000 到 U + FFFF 范围 内 的 任何 码 位 都 将 直接 存储 ;任何 超出 这些 范围 的 值 都 将 根据 UTF - 16 编码 规则 拆分 为 两 个 16 位 值 。
例如 , 0x24B62 将 被 编码 为 0xd892 、 0xdf62 。
您 可以 将 字符 串 转换 为 您 喜欢 的 任何 方式 , 但 Windows API 仍然 需要 并 提供 UTF - 16 , 因此 这 可能 是 最 方便 的 。
kmpatx3s3#
wchar_t
的大小和含义是由实现定义的。在Windows上,它是16位的,就像你说的,在类Unix系统上,它通常是32位的,但并不总是如此。就这一点而言,允许编译器做自己的事情,并为
wchar_t
选择与系统所说的不同的大小--它只是与系统的其余部分不兼容。C++11提供了
std::u32string
,用于表示unicode代码点的字符串。我相信最近的Microsoft编译器也包含了它。它的使用有些有限,因为Microsoft的系统函数需要16位宽的字符(也叫UTF-16 le),而不是32位的unicode代码点(也叫UTF-32,UCS-4)。不过,您提到了UTF-8:UTF-8编码的数据可以存储在一个常规的
std::string
中。当然,由于它是一个可变长度的编码,你不能通过索引访问unicode码点,你只能通过索引访问字节。但是你通常编写的代码不需要通过索引访问码点。即使使用u32string
.Unicode码位也不会与可打印字符一一对应(“grapheme”)因为Unicode中存在组合标记,所以在学习编程时,您在字符串上玩的许多小把戏(反转它们,搜索子字符串)并不容易处理Unicode数据,无论您将其存储在什么中。如𤭢您所说,该字符是\u24B62。它是UTF-8编码的一系列 * 四 * 字节,而不是三个:F0 A4 AD A2.在UTF-8编码数据和unicode码位之间进行转换比较费力(当然,这并不需要很大的努力,库函数会为您完成)。最好将“编码数据”和“unicode数据”视为不同的东西。您可以使用任何您认为最方便的表示,直到您需要(例如)将文本呈现到屏幕上。此时,你需要将它(重新)编码为你的输出目标理解的编码。
dgtucam14#
在标准C++中,我们有char和wchar_t来存储字符-char可以存储0x 00到0xFF之间的值。而wchar_t可以存储0x 0000到0xFFFF之间的值
不完全是:
Unicode字符最多占用4字节的空间。
不完全是。Unicode不是一种编码。Unicode是定义每个码位是什么的标准,码位被限制在21位。前16位定义了字符在代码纯文本上的位置,而随后的5位定义了字符在哪个纯文本上。
有几种unicode * 编码 *(UTF-8,UTF-16和UTF-32是最常见的)这是你在内存中存储字符的方式。这三种编码之间有实际的区别。
就我个人而言,我使用UTF-8进行传输和存储,使用UTF-32在内存中表示文本。
qco9c6ql5#
char
和wchar_t
不是唯一用于文本字符串的数据类型。C++11引入了新的char16_t
和char32_t
数据类型以及std::basic_string
的各自的STLstd::u16string
和std::u32string
类型定义,以解决wchar_t
类型的模糊性。其在不同的平台上具有不同的大小和编码。wchar_t
在某些平台上是16位的,适合UTF-16编码,但在其他平台上是32位的,而适合UTF-32编码。char16_t
在所有平台上都是16位UTF-16,char32_t
在所有平台上都是32位UTF-32。