【问题标题】:Loop through Unicode string as character循环遍历 Unicode 字符串作为字符
【发布时间】:2020-02-16 07:08:12
【问题描述】:

使用以下字符串,大小输出不正确。为什么会这样,我该如何解决?

string str = " ██████";
cout << str.size();
// outputs 19 rather than 7

我正在尝试逐个字符地循环遍历str,以便将其读入vector&lt;string&gt;,它的大小应该为7,但我不能这样做,因为上面的代码输出19。

【问题讨论】:

  • 您可以使用wstring 而不是string
  • @Jean-BaptisteYunès 如果wstring 使用 UTF-16 并且字符在 BMP 之外,则该方法将不起作用。它也不适用于许多带有变音符号的字符,因为 some are precomposed and some can be combined: Å = U+00C5 或 U+0041 加上 U+030A 并且没有适当的规范化调用 length 可能会返回 1 或 2 而不是一个一致的数字预计
  • @phuclv 看看接受的答案。
  • @Jean-BaptisteYunès 我昨天已经看到了,如果 OP 想要的是字符数是不正确的
  • @phuclv 我打错了。我正在寻找 7 的输出 - 该帖子已被编辑。我有兴趣使用其他 Unicode 字符,例如 ,以及可能的其他方框图字符。我正在查找字符串中的字符数 - 在您提到的情况下,我希望返回 1

标签: c++ string unicode string-length


【解决方案1】:

TL;DR

basic_stringsize()length() 成员返回基础字符串的单位 大小,不是 的数量可见字符。要获得预期的数字:

  • 对于不包含非 BMP、不包含 combining characters 和不包含 joining characters 的非常简单的字符串,使用带有 u 前缀的 UTF-16
  • 对于不包含任何组合或连接字符的非常简单的字符串,使用带有 U 前缀的 UTF-32
  • 规范化字符串并计算任意 Unicode 字符串

" ██████" 是一个空格,后跟一系列 6 个U+2588 字符。您的编译器似乎将UTF-8 用于std::string。 UTF-8 是variable-length encoding 并且许多字母使用多个字节进行编码(因为显然你不能仅用一个字节编码超过 256 个字符)。在 U+0800 和 U+FFFF 之间的 UTF-8 代码点由 3 个字节编码。因此 UTF-8 中字符串的长度为 1 + 6*3 = 19 个字节。

您可以使用任何 Unicode 转换器(如 this one)检查字符串是否以 UTF-8 编码为 20 E2 96 88 E2 96 88 E2 96 88 E2 96 88 E2 96 88 E2 96 88,您还可以遍历字符串的每个字节进行检查

如果你想要字符串中的可见个字符的总数,那么这会很棘手,并且 churill 的解决方案不起作用。阅读Twitter中的示例

如果您使用最基本的字母、数字和标点符号之外的任何内容,情况会变得更加混乱。虽然许多人使用多字节汉字来举例说明这些问题,但 Twitter 发现重音元音会引起最大的混乱,因为说英语的人只是希望它们能正常工作。举个例子:“咖啡馆”这个词。事实证明,有两个字节序列看起来完全相同,但使用的字节数不同:

café  0x63 0x61 0x66 0xC3 0xA9        Using the “é” character, called the “composed character”.
café  0x63 0x61 0x66 0x65 0xCC 0x81   Using the combining diacritical, which overlaps the “e”

您需要一个 Unicode 库,例如 ICUnormalize 的字符串和计数。例如 Twitter 使用 Normalization Form C

编辑:

由于您只对似乎不在 BMP 之外且不包含任何组合字符的方框图字符感兴趣,因此 UTF-16 和 UTF-32 可以工作。与std::string 一样,std::wstring 也是basic_string,并且没有强制编码。在大多数实现中,它通常是 UTF-16 (Windows) 或 UTF-16 (*nix),因此您可以使用它,但它不可靠并且取决于源代码编码。更好的方法是使用std::u16string (std::basic_string&lt;char16_t&gt;) 和std::u32string (std::basic_string&lt;char32_t&gt;)。无论源文件的系统和编码如何,它们都可以工作

std::wstring wstr     = L" ██████";
std::u16string u16str = u" ██████";
std::u32string u32str = U" ██████";
std::cout << str.size();    // may work, returns the number of wchar_t characters
std::cout << u16str.size(); // always returns the number of UTF-16 code units
std::cout << u32str.size(); // always returns the number of UTF-32 code units

如果您对如何解决所有 Unicode 字符感兴趣,请继续阅读下文

上面提到的“café”问题引发了一个问题,即如何计算 Tweet 字符串“café”中的字符数。对人眼来说,长度显然是四个字符。根据数据的表示方式,这可能是五个或六个 UTF-8 字节。 Twitter 不想因为我们使用 UTF-8 或所讨论的 API 客户端使用更长的表示这一事实而惩罚用户。因此,无论发送哪种表示形式,Twitter 都将“café”计为四个字符。

[...]

Twitter 使用文本的规范化表格 C (NFC) 版本计算推文的长度。这种类型的规范化有利于使用完全组合的字符(咖啡馆示例中的 0xC3 0xA9)而不是长格式版本(0x65 0xCC 0x81)。 Twitter 还计算文本中的代码点数,而不是 UTF-8 字节数。咖啡馆示例中的 0xC3 0xA9 是一个代码点 (U+00E9),在 UTF-8 中编码为两个字节,而 0x65 0xCC 0x81 是两个代码点,编码为三个字节

Twitter - Counting characters

另见

【讨论】:

    【解决方案2】:

    std::string 只包含 1 字节长的字符(通常是 8 位,包含 UTF-8 字符),你需要 wchar_tstd::wstring 来实现你想要的:

    std::wstring str = L" ██████";
    std::cout << str.size();
    

    尽管这会打印 7(一个空格和 6 个 unicode 字符)。注意字符串文字前面的 L,所以它会被解释为一个宽字符串。

    【讨论】:

    • 从技术上讲,char 不需要为 8 位长。某些平台可能有更长的char
    • @churill std::string 对 UTF-8 一无所知,除非你真的让它保持 UTF-8,否则不会“通常”保持 UTF-8。是否使用 UTF-8 实际上取决于所使用的平台、编译器设置、源文件的编码等。至少,如果你想要 UTF-8 中的文字,请使用 u8 前缀:@ 987654329@ 但我提到的其他因素仍然起作用。
    • @RemyLebeau 我明白了,我不擅长找到正确的术语,请随意编辑 :)
    • 如上所述,这将返回 UTF-16 的数量(或 UTF-32,具体取决于实现)代码单元而不是 字符串中的字符数(如果这是 OP 想要的,因为目前还不清楚)所以它绝对不适用于像 "??‍?Å" 这样的字符串(对于 UTF-16 和 6,它将返回 9对于 UTF-32)
    猜你喜欢
    • 2015-01-04
    • 2016-01-24
    • 1970-01-01
    • 1970-01-01
    • 2014-09-19
    • 2023-03-23
    • 1970-01-01
    • 2023-02-21
    相关资源
    最近更新 更多