【问题标题】:How to manage Unicode strings easily in C++如何在 C++ 中轻松管理 Unicode 字符串
【发布时间】:2020-05-25 16:13:57
【问题描述】:

我想从 Unicode 字符串中获取每个字符。 如果这个问题不好,希望您的理解。

string str = "öp";
for (int i = 0; i < str.length(); i++) {
 cout << str[i] << endl;
}

在这种情况下,str[0] 是一个损坏的字符,因为ö 的长度是 2。 我该如何管理它?我真的很感谢你的回答。谢谢。

【问题讨论】:

  • @AlexF 不,绝对不要那样做。 wchar_t 彻底坏掉了。
  • Qt QString 类对此非常有用。
  • 如果你在 Linux 上,我认为字符串(确切地说是char)无论如何都是符合 unicode 的。
  • @theWiseBro 大多数现代 Linux 发行版确实使用 unicode 和 default 的 UTF-8 编码。这并不意味着他们总是这样做。你可以改变它,程序应该能够应付。
  • @theWiseBro 不是,也不能。 char 只是一个字节,char 字符串用作字节缓冲存储。它们与编码无关。这意味着它们是一种合适的存储介质,可以表示所有可能的 Unicode 代码点,但它们不允许对数据进行特定于编码的访问。特别是,访问单个 chars 不一定解析单个 Unicode 代码点或字形,这是 OP 想要的。您需要使用支持 Unicode 的文本库。

标签: c++ string unicode


【解决方案1】:

string 对象的“原子”单元显然是另一个string(包含单个代码点)或char32_t (Unicode 代码点)。 string 是最有用的,因为可以再次编写它,并且不需要 UTF 转换。

我对 C/C++ 有点生疏了,但是类似:

string utf8_codepoint(const string& s, int i) {

    // Skip continuation bytes:
    while (s[i] & 0xC0 == 0x80) {
        ++i;
    }

    string cp = s[i];
    if (s[i] & 0xC0 == 0xC0) { // Start byte.
        ++i;
        while (s[i] & 0xC0 == 0x80) { // Continuation bytes.
            cp += s[i];
            ++i;
        }
    }
    return cp;
}

for (size_t i = 0; i < str.length(); i++)
   wcout << utf8_codepoint(str, i) << endl;

for (size_t i = 0; i < str.length(); ) {
   string cp = utf8_codepoint(str, i);
   i += cp.length();
   wcout << cp << endl;
}

当然,Unicode 中的零宽度重音符号不能单独打印, 但同样适用于控制字符,或者没有完全支持 Unicode 的字体(因此字体大小约为 35 MB)。

【讨论】:

  • 不是size_t。这可能比需要的大,并且不能保证足够大。
  • @Deduplicator uint32?我不知道当前的趋势。 char32_t 我明白了。
【解决方案2】:

为了在 UTF-8 字符串的字符之间插入字符(例如您在示例中尝试的换行符),您只能在完整的字素簇之间执行此操作。现在您在不完整的代码点之后添加换行符,这会破坏编码。


Unicode 标准是here。请特别参阅本节:

3.9 Unicode 编码形式

UTF-8

表 3-6。 UTF-8 位分布

+----------------------------+------------+-------------+------------+-------------+
|        Scalar Value        | First Byte | Second Byte | Third Byte | Fourth Byte |
+----------------------------+------------+-------------+------------+-------------+
| 00000000 0xxxxxxx          | 0xxxxxxx   |             |            |             |
| 00000yyy yyxxxxxx          | 110yyyyy   | 10xxxxxx    |            |             |
| zzzzyyyy yyxxxxxx          | 1110zzzz   | 10yyyyyy    | 10xxxxxx   |             |
| 000uuuuu zzzzyyyy yyxxxxxx | 11110uuu   | 10uuzzzz    | 10yyyyyy   | 10xxxxxx    |
+----------------------------+------------+-------------+------------+-------------+

根据这些,我们可以设计以下算法来迭代代码点:

for (int i = 0; i < str.length();) {
    std::cout << str[i];

    if(str[i] & 0x80) {
        std::cout << str[i + 1];
        if(str[i] & 0x20) {
            std::cout << str[i + 2];
            if(str[i] & 0x10) {
                std::cout << str[i + 3];
                i += 4;
            } else {
                i += 3;
            }
        } else {
            i += 2;
        }
    }  else {
        i += 1;
    }
    
    std::cout << std::endl;
}

如果它以组合形式标准化,即"ö" 是单个代码点,则此简单算法对于您的示例就足够了。然而,对于一般用途,需要更复杂的算法来区分字素簇。

此外,这种简单的算法不会检查无效序列,并且在这种情况下可能会溢出输入字符串。这只是一个简单的示例,不适合生产使用。对于生产用途,我建议使用外部库。

【讨论】:

  • 这会分解代码点,但不会处理字素集群。如果有人用“o”“组合分音符号”来制作“ö”,那么这仍然会将两者分开并仍然产生“破碎字符”
  • @PeterT 哦,该死的。好像使用别人的实现会更好:)我会在答案中添加解释,但我不会实现。
  • @eerorika re: C++ standard library has no functionality to help iterate code points of unicode or any other variable width encoding en.cppreference.com/w/cpp/locale/codecvt/length 允许您遍历代码点,因为您可以找出编码下一个代码点的字节数。不过,这并没有让迭代字素集群变得更容易。我在尝试写一个例子的时候已经意识到了,所以我不打算自己做一个答案,但它的开头可以找到here
  • @dovvei 我会从答案中删除索赔。也就是说,据我了解,示例中使用的std::locale("en_US.utf8") 不是很便携,因为语言环境名称是系统特定的。
  • 规范化是切线的,你想要的完全组合成单个代码点,NFC and NFKC 通常是。但这并不总是可能的。
【解决方案3】:

问题在于 utf-8 (not unicode) 是一种多字节字符编码。最常见的字符(ansi 字符集)只使用一个字节,但不太常见的字符(特别是表情符号)最多可以使用 4 个。但这远不是唯一的问题。

如果您只使用来自Basic Multilingual Plane 的字符,并且可以确保永远不会遇到组合 字符,那么您可以安全地使用std::wstringwchar_t,因为wchar_t 可以保证包含 BMP 中的任何字符。

但在一般情况下,Unicode 是一团糟。即使使用可以包含任何 unicode 代码点的char32_t,您也不能确定 unicode 代码点和字素(显示的字符)之间存在双射。例如,带有 ACUTE 的拉丁小写字母 E (é) 是 Unicode 字符 U+E9。但它可以用分解的形式表示为 U+65 U+0301,或拉丁小写字母 E 后跟一个组合重音。因此,即使使用char32_t,一个字素也有 2 个字符,拆分它们是不正确的:

wchar32_t eaccute = { 'e', 0x301, 0};

这确实是 的代表。可以复制粘贴来控制不是U+E9字符,而是分解后的字符,但打印出来不能有区别。

TL/DR:除非您确定只使用 Unicode 字符集的子集,该子集可以用更短的字符集表示为 ISO-8859-1 (Latin1) 或等效字符,否则您没有简单的方法可以知道如何将字符串拆分为真实字符。

【讨论】:

  • 说句公道话,语言是一团糟,而 Unicode 勇敢地尝试在糟糕的交易中做到最好。
  • @Deduplicator:我知道,Unicode 从 Windows 版本发展到当前版本,并添加了许多内容。但正因为如此,没有简单的 page 分解可以轻松识别 0 宽度代码点,这足以解决 OP 问题。
猜你喜欢
  • 2010-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-22
  • 2015-03-31
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
相关资源
最近更新 更多