如何在C++中删除UTF-8字符串的最后一个字符?

7

文本存储在 std::string 中。

如果文本是8位ASCII码,那么这很容易:

text.pop_back();

但如果它是UTF-8文本呢?据我所知,标准库中没有我可以使用的与UTF-8相关的函数。


4
“character”是什么意思?代码单元?代码点?字形簇? - IInspectable
3
std::basic_string应该如何帮助?您需要一个UTF库来处理UTF文本编码。 - Lightness Races in Orbit
2
要么找一个好的utf8库,要么学习utf8结构并自己实现。 - kmdreko
3
我认为您错过了 @IInspectable 的观点。在 Unicode 中,“字符”是一个不明确的概念,无论使用哪种编码方法(UTF8、UTF16 等)。这就是为什么他们要求您澄清“字符”一词的含义。需要提前警告的是:标准库基本上不支持 Unicode。您需要转向一个专门的 Unicode 库。 - aruisdante
2
@self:我相信你严重低估了 Unicode 的复杂性。那么 U+0301(组合重音符号)呢?或者 U+200C(零宽度非连接符)呢?U+FB00(拉丁文小型连字 ff)又如何?每个字符都被分配了一个代码点,但没有一个被视为字符。 - IInspectable
显示剩余8条评论
2个回答

8

如果要使用 UTF-8,你真的需要一个 UTF-8库。但是对于这个任务,我认为类似这样的东西可能足够了:

void pop_back_utf8(std::string& utf8)
{
    if(utf8.empty())
        return;

    auto cp = utf8.data() + utf8.size();
    while(--cp >= utf8.data() && ((*cp & 0b10000000) && !(*cp & 0b01000000))) {}
    if(cp >= utf8.data())
        utf8.resize(cp - utf8.data());
}

int main()
{
    std::string s = "κόσμε";

    while(!s.empty())
    {
        std::cout << s << '\n';
        pop_back_utf8(s);
    }
}

输出:

κόσμε
κόσμ
κόσ
κό
κ

它依赖于这样一个事实:UTF-8编码有一个起始字节,后面跟着几个连续字节。使用提供的位运算符可以检测到这些连续字节

2
如果您正在使用组合字形,则这仍然是一个问题:κόσÄÄμε κόσÄÄμ κόσÄÄ κόσÄA κόσÄ κόσ κό κ - Taywee
@CoryNelson,我刚刚在阅读有关它们的内容。我认为它们似乎在不同的层面上运作。弹出代码点基本上是UTF-8,而组合字符似乎是应用程序可能使用的约定。我认为这可能取决于应用程序层面来决定弹出多少代码点以删除给定的字形。 - Galik
@Taywee 我现在和你在一起。由于某种原因,我的代码似乎可以很好地处理你提供的示例字符串。也许在传输过程中,某些内容已经被规范化为仅包含自包含的内容?我认为将代码点组合起来是基本上不同的层次。UTF8 可以保留代码点值供应用程序使用以构建组合字形,但它并没有为每种语言定义它们。我认为应用程序应该根据它们所处理的语言适当地应用这样的约定,而不是像文本处理原语这样。 - Galik
很有可能stackoverflow会对评论等输入进行规范化处理。这个解决方案不错,我只是想指出,根据输入的不同,它可能会产生意想不到的影响(特别是在一些情况下,比如零宽度Unicode字符)。我非常确定,使用真正的Unicode库也会遇到相同的问题,因为它仍然将每个码点作为自己的字符来处理。 - Taywee
1
@CoryNelson,顺便说一下,删除最后一个字形簇可能会导致字符串失去意义。这完全取决于所讨论的含义是什么。 - R. Martinho Fernandes
显示剩余3条评论

3
你可以不断弹出字符,直到达到代码点的前导字节。在UTF8中,代码点的前导字节是以下两种模式之一:0xxxxxxx11xxxxxx,所有非前导字节都是10xxxxxx的形式。这意味着你可以检查第一个和第二个比特位来确定是否有前导字节。
bool is_leading_utf8_byte(char c) {
    auto first_bit_set = (c & 0x80) != 0;
    auto second_bit_set = (c & 0X40) != 0;
    return !first_bit_set || second_bit_set;
}

void pop_utf8(std::string& x) {
    while (!is_leading_utf8_byte(x.back()))
        x.pop_back();
    x.pop_back();
}

当然,这并没有进行错误检查,假设您的字符串是有效的utf-8。


为什么 first_bit_setsecond_bit_set 是自动变量,而不是布尔类型? - Iter Ator
3
这对于最简单的情况是有效的。请注意,如果您使用组合字符,则此方法将失败,因为它只会从其字形中删除组合字符,而不是完全删除字形。 - Taywee
1
@IterAtor:因为我总是使用auto。你不必这样做。 - Benjamin Lindley
2
你可以将 is_leading_utf8_byte 简化为 return (c & 0xC0) != 0x80,以测试它是否不是一个尾随字节 (10xxxxxx)。 - Mark Tolonen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接