【问题标题】:String to Unicode, and Unicode to decimal code point (C++)字符串到 Unicode,Unicode 到十进制代码点 (C++)
【发布时间】:2017-07-25 14:16:22
【问题描述】:

尽管在论坛上看到很多关于 unicode 和字符串转换(在 C/C++ 中)的问题,并且在该主题上搜索了几个小时,但我仍然无法找到对我来说似乎是一个非常基本的过程的直接解释.这是我想做的:

  • 我有一个字符串,它可能使用任何可能语言的任何字符。让我们以西里尔文为例。所以说我有: std::string str = "сапоги";

  • 我想遍历组成该字符串的每个字符并且:

    • 知道/打印字符的 Unicode 值
    • 将该 Unicode 值转换为十进制值

我真的在谷歌上搜索了好几个小时,却找不到一个直接的答案。如果有人能告诉我如何做到这一点,那就太好了。

编辑

所以我设法做到了:

#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <locale>
#include <codecvt>
#include <iomanip>

// utility function for output
void hex_print(const std::string& s)
{
    std::cout << std::hex << std::setfill('0');
    for(unsigned char c : s)
        std::cout << std::setw(2) << static_cast<int>(c) << ' ';
    std::cout << std::dec << '\n';
}

int main()
{
    std::wstring test = L"сапоги";

    std::wstring_convert<std::codecvt_utf16<wchar_t>> conv1;
    std::string u8str = conv1.to_bytes(test);
    hex_print(u8str);

    return 1;
}

结果:

04 41 04 30 04 3f 04 3e 04 33 04 38 

Code

哪个是正确的(它映射到 unicode)。问题是我不知道我是否应该使用 utf-8、16 或其他东西(正如 Chris 在评论中指出的那样)。有没有办法让我知道呢? (无论它最初使用什么编码或需要使用什么编码?)

编辑 2

我想我会通过第二次编辑来解决一些 cmets:

“将 Unicode 值转换为十进制值” 为什么?

我会解释原因,但我也想以友好的方式发表评论,我的问题不是“为什么”而是“如何”;-)。您可以假设 OP 有理由提出这个问题,但当然,我理解人们对为什么感到好奇......所以让我解释一下。我需要这一切的原因是因为我最终需要从字体文件中读取字形(TrueType OpenType 无关紧要)。碰巧这些文件有一个名为cmap 的表,它是某种关联数组,将字符的值(以代码点的形式)映射到字体文件中字形的索引。表中的代码点不是使用符号 U+XXXX 定义的,而是直接在该数字的十进制对应物中定义的(假设 U+XXXX 符号是 uint16 数字的十六进制表示[或 U+XXXXXX,如果大于 uint16 但更多稍后再说])。因此,总而言之,西里尔字母 ([gueu]) 中的字母 г 具有代码点值 U+0433,十进制形式为 1075。我需要值 1075cmap 表中进行查找。

// utility function for output
void hex_print(const std::string& s)
{
    std::cout << std::hex << std::setfill('0');
    uint16_t i = 0, dec;
    for(unsigned char c : s) {
        std::cout << std::setw(2) << static_cast<int>(c) << ' ';
        dec = (i++ % 2 == 0) ? (c << 8) : (dec | c);
        printf("Unicode Value: U+%04x Decimal value of code point: %d\n", codePoint, codePoint);
    }
}

std::string 与编码无关。它本质上存储字节。 std::wstring 很奇怪,虽然也没有定义为保存任何特定的编码。在 Windows 中,wchar_t 用于 UTF-16

是的,我认为当你理解“虽然”你认为(至少我是这样认为)字符串只是存储“ASCII”字符时(坚持),这似乎是错误的。事实上,评论所建议的 std::string 似乎只存储“字节”。虽然很明显,如果您查看字符串 english 的字节,您会得到:

std::string eng = "english";
hex_print(eng);
65 6e 67 6c 69 73 68

如果你用“сапоги”做同样的事情,你会得到:

std::string cyrillic = "сапоги";
hex_print(cyrillic );
d1 81 d0 b0 d0 bf d0 be d0 b3 d0 b8

我真正想知道/理解的是这种转换是如何隐式完成的?为什么在这里使用 UTF-8 编码而不是 UTF-16,是否有可能改变它(或者是由我的 IDE 或操作系统定义的?)?显然,当我在文本编辑器中复制粘贴字符串 сапоги 时,它实际上已经复制了一个 12 个字节的数组(这 12 个字节可能是 utf-8 或 utf-16)。

我认为 Unicode 和编码之间存在混淆。代码点(AFAIK)只是一个字符代码。 UTF 16 为您提供代码,因此您可以说您的 0x0441 是西里尔小写字母 es 的 с 代码点。据我了解,UTF16 与 Unicode 代码点一对一地映射,Unicode 代码点的范围为 1M 和一些字符。但是,其他编码技术,例如 UTF-8 并不直接映射到 Unicode 代码点。所以,我想,你最好坚持使用 UTF-16

没错!我发现这条评论确实非常有用。因为是的,关于你对 Unicode 代码点值的编码方式与 Unicode 值本身无关这一事实存在混淆(我很困惑),这有点像因为事实上事情可能会像我一样误导现在显示。 You can indeed encode the string сапоги using UTF8 and you will get:

d1 81 d0 b0 d0 bf d0 be d0 b3 d0 b8

很明显,它确实与字形的 Unicode 值无关。现在,如果您使用 UTF16 编码相同的字符串,您会得到:

04 41 04 30 04 3f 04 3e 04 33 04 38

其中 04 和 41 确实是字母 с(西里尔字母 [se])的两个字节(十六进制形式)。至少在这种情况下,unicode 值与其 uint16 表示之间存在直接映射。这就是为什么(根据维基的解释 [source]):

UTF-16 和 UCS-2 都将此范围内的代码点编码为单个 16 位代码单元,在数字上等于相应的代码点。

但正如评论中有人建议的那样,某些代码点值超出了您可以用 2 个字节定义的值。例如:

1D307 ????完整圆的 TETRAGRAM (Tai Xuan Jing Symbols)

这就是这条评论所暗示的:

据我所知,UTF-16 不会涵盖所有字符,除非您使用代理对。本来是打算的,当 65k 绰绰有余的时候,结果就跑偏了,现在是一个非常尴尬的选择

虽然是完全精确的 UTF-16,如 UTF-8 CAN 编码 ALL 字符,但它最多可以使用 4 个字节(正如你所建议的那样如果需要超过 2 个字节,请使用代理对)。

我尝试使用 mbrtoc32 转换为 UTF-32,但在 Mac 上奇怪地缺少 cuchar

顺便说一句,如果你不知道surrogate pair 是什么(我不知道),那么a nice post about this on the forum

【问题讨论】:

  • 你想使用类似std::string str = L"сапоги"的东西吗?
  • 我不知道。我的目标是找到组成字符串的每个字符的 unicode 值并将其转换为十进制值。
  • 请提供minimal reproducible example。你现在的例子我太模糊了。
  • 您需要知道字符串的编码(例如,UTF-8),然后最好找到一个允许您遍历代码点的库。

标签: c++ string unicode


【解决方案1】:

出于您的目的,查找和打印每个字符的值,您可能希望使用char32_t,因为它没有多字节字符串或代理对,只需转换为unsigned long 即可转换为十进制值.我会链接到我写的一个例子,但听起来好像你想自己解决这个问题。

C++14 直接支持 char8_tchar16_tchar32_t 类型,除了旧的 wchar_t 有时表示 UCS-32,有时表示 UTF-16LE,有时表示 UTF-16BE,有时表示什么不同的。它还允许您在运行时存储字符串,无论您将源文件保存在什么字符集中,以任何这些格式使用u8"u"U" 前缀,以及\uXXXX unicode 转义作为倒退。为了向后兼容,您可以在 unsigned char 数组中使用十六进制转义码对 UTF-8 进行编码。

因此,您可以以任何您想要的格式存储数据。您也可以使用 facet codecvt&lt;wchar_t,char,mbstate_t&gt;,所有语言环境都需要支持。 &lt;wchar.h&gt;&lt;uchar.h&gt;中还有多字节字符串函数。

我强烈建议您以 UTF-8 存储所有新的外部数据。这包括您的源文件! (令人讨厌的是,一些较旧的软件仍然不支持它。)在内部使用与库相同的字符集也可能很方便,在 Windows 上将是 UTF-16 (wchar_t)。如果您需要可以保存任何代码点且没有特殊情况的固定长度字符,char32_t 会很方便。

【讨论】:

    【解决方案2】:

    最初的计算机是为美国市场设计的,并使用 Ascii - 美国信息交换代码。它有 7 位代码,只有基本的英文字母和一些标点符号,以及用于驱动纸张和墨水打印机终端的低端代码。 随着计算机的发展并开始用于语言处理和数字工作,这变得不够了。发生的第一件事是提出了对 8 位的各种扩展。这可以覆盖大多数装饰的欧洲字符(口音等),也可以提供一系列有利于创建菜单和面板的基本图形,但您无法同时实现两者。仍然没有办法表示像希腊语这样的非拉丁字符集。 因此提出了一种 16 位代码,称为 Unicode。 Microsoft 很早就采用了这一点,并发明了 wchar WCHAR(它有各种标识符)来保存国际字符。然而,16 位不足以容纳所有常用字形,而且 Unicode 联盟还引发了与 Microsoft 的 16 位代码集的一些轻微不兼容性。

    所以 Unicode 可以是一系列 16 位整数。那是 wchar 字符串。 Ascii 文本现在在高字节之间有零个字符,因此您不能将宽字符串传递给函数 expectign Ascii。由于 16 位几乎但还不够,因此还生成了 32 位 unicode 集。

    但是,当您将 unicode 保存到文件时,这会产生问题,它是 16 位还是 32 位> 是大端还是小端。因此,建议在数据开始处设置一个标志来解决这个问题。问题是文件内容,内存,不再匹配字符串内容。

    C++ std:;string 被模板化,因此它可以使用基本字符或宽类型之一,在实践中几乎总是 Microsoft 的 16 位接近 unicode 编码。

    UTF-8 的发明是为了提供帮助。这是一种多字节可变长度编码,它使用 ascii 只有 7 位这一事实。因此,如果设置了高位,则意味着字符中有两个、三个或四个字节。现在非常多的字符串是英语或主要是人类可读的数字,所以本质上是 ascii。这些字符串在 Ascii 中和 UTF-8 中是一样的,这让生活变得更加轻松。您没有字节顺序约定问题。您确实有一个问题,您必须将 UTF-8 解码为具有不完全微不足道的功能的代码点,并记住将您的读取位置提前正确的字节数。

    UTF-8 确实是答案,但其他编码仍在使用中,您会遇到它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-08
      • 2015-12-06
      • 1970-01-01
      • 2014-12-28
      • 2018-07-04
      • 2011-06-10
      • 2021-10-20
      相关资源
      最近更新 更多