【问题标题】:How to get correct length of std::u8string in C++?如何在 C++ 中获得正确的 std::u8string 长度?
【发布时间】:2020-04-28 16:42:34
【问题描述】:

如何获得正确的 std::u8string 长度? (在 C++20 中) 我尝试了以下代码,这些代码打印出不正确的长度值,这可能会返回代码点数的值。

我怎样才能得到我期望 7 个字符的正确值?

int main() {
    const char8_t* s = u8"Hello????????";
    auto st = std::u8string(s);
    std::cout << st.size() << std::endl;
}

【问题讨论】:

  • 我认为size()length() 对于std::u8string 将返回字符串中代码点 的数量,而不是打印字符的数量。您或许可以尝试转换为 std::u32string 以确保所有多字节代码仅计为单个字符。
  • 你打算用这个号码做什么,一旦获得?请注意,在组合使用变音符号和连字的情况下,代码点的数量可能与字素的数量(人类认为是“字符”的显示单位)不对应。
  • 我想要多个显示单元。
  • @KiYugadgeter:请注意,Unicode 代码点的数量等于字符串的“显示单位”。这需要复杂的文本布局,这是一个更加复杂的计算。
  • @AdrianMole "我认为size()length() 对于std::u8string 都会返回字符串中“代码点”的数量“-不,它们返回编码的“代码单元”的数量。 “您或许可以尝试转换为 std::u32string,以确保所有多字节代码仅计为单个“字符”。” - 您所说的“字符”是事实上,“代码点”。您在视觉上看到的是代码点组,称为“字素簇”。见What's the difference between a character, a code point, a glyph and a grapheme?

标签: c++ string unicode c++20


【解决方案1】:

就大多数 C++ 函数而言,u8string 实际上是一个字节序列。因此size() 给你 13 (48 65 6c 6c 6f f0 9f 98 83 f0 9f 98 83)。 “?”(“张开嘴的笑脸”U+1F603)被编码为 4 个元素 f0 9f 98 83[i]substr 等也会看到这一点。

知道是UTF-8,就可以数出Unicode码位的个数了。您可以使用u32string,它是代码点。我不相信 C++ 具有直接在 u8string 开箱即用的功能:

size_t count_codepoints(const std::u8string &str)
{
    size_t count = 0;
    for (auto &c : str)
        if ((c & 0b1100'0000) != 0b1000'0000) // Not a trailing byte
            ++count;
    return count;
}

然而,这可能仍然不是人们所认为的“字符数”。这是因为可以使用多个代码点来表示单个可见字符,即“组合字符”。其中一些还具有“预先组合”的形式,并且组合代码点的顺序可能会有所不同,从而导致“正常形式”和比较 Unicode 字符串时出现问题。例如,“Á”可能是“LATIN CAPITAL LETTER A WITH ACUTE”(U+00C1),它是 UTF-8 C3 81,或者它可能有一个普通的“A”和“COMBINING ACUTE ACCENT (U+0301)” " 这是两个代码点和 3 个 UTF-8 字节 41 CC 81

unicode.org 中的每个 Unicode 版本都有表格,可让您正确处理和转换组合字符(以及大小写转换等),但它们非常广泛,您需要编写一些代码来处理它们.第三方库(我认为 Linux 主要使用 ICU)或 OS 功能(Window 有一堆 API)也提供各种实用程序。

值得注意的是,您可以在许多其他情况/语言中遇到这些问题,而不仅仅是 C++。例如JavaScript、Java 和 .NET 以及 Windows C/C++ API(在 Windows 上基本上是 wchar_t)使用 UTF-16 字符串,这些字符串对于某些代码点具有“代理对”,其中许多函数实际上计算 UTF-16 元素,而不是代码点。

【讨论】:

【解决方案2】:

标准的 c++ 答案是将字符串从 utf8 转换为 utf32,然后检查大小。

令人担忧的是,std::wstring_convert 从 c++17 开始已被弃用。我不知道替换会是什么。

#include <string>
#include <iostream>
#include <cstdlib>
#include <locale>
#include <codecvt>

auto convert(std::u8string input) -> std::u32string
{
    auto first = reinterpret_cast<const char*>(input.data());
    auto last = first + input.size();

    auto result = std::u32string();

    std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> ucs4conv;
    try
    {
        result = ucs4conv.from_bytes(first, last);
    }
    catch(const std::range_error& e) {
        last = first + ucs4conv.converted();
        std::clog << "UCS4 failed after consuming " << std::dec << std::distance(first, last) <<" characters:\n";
        result = ucs4conv.from_bytes(first, last);
    }

    return result;
}

int main() {
    const char8_t* s = u8"Hello??";
    auto st = std::u8string(s);
    std::cout << "bytes      : " << st.size() << std::endl;

    auto ws = convert(st);
    std::cout << "wide chars : " << ws.size() << std::endl;
}

预期输出:

bytes      : 13
wide chars : 7

https://godbolt.org/z/Z0a6bb

【讨论】:

    【解决方案3】:

    其他答案已经建议了计算代码点数量的方法,如果这确实是您的用例所需要的。我添加这个答案是为了表明代码点长度可能不是你想要的。

    实际上,我自己不会说明这一点。相反,我将提供一个指向解释问题的优秀博客文章的链接,以便您评估您实际需要的信息。

    https://hsivonen.fi/string-length

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-11
      • 2021-05-03
      • 2019-11-07
      • 2021-08-18
      • 1970-01-01
      • 2010-12-06
      • 1970-01-01
      • 2022-12-15
      相关资源
      最近更新 更多