【问题标题】:C++ substring multi byte charactersC++子串多字节字符
【发布时间】:2012-06-06 11:57:48
【问题描述】:

我有这个 std::string 包含一些跨越多个字节的字符。

当我在这个字符串上做一个子字符串时,输出是无效的,因为当然,这些字符被计为 2 个字符。在我看来,我应该改用 wstring,因为它将这些字符存储为一个元素而不是更多。

所以我决定将字符串复制到一个 wstring 中,但这当然没有意义,因为字符仍然分成 2 个字符。这只会使情况变得更糟。

将字符串转换为 wstring 有没有好的解决方案,将特殊字符合并为 1 个元素而不是 2 个元素。

谢谢

【问题讨论】:

  • 这是一个相关的问题:stackoverflow.com/q/4804298/1158895
  • 你的字符串的编码是什么?我假设 UTF-8
  • @SirDarius,确实是 UTF-8。但我认为这个问题可以适用于任何一个字符需要多个字节的编码,不是吗?
  • 当然可以,但是知道这一点很重要,因为某些编码需要字符大至 32 位的宽字符串。您可能想要使用 libiconv 等库。
  • @W.Goeman:一个重要的问题:std::wstring 不幸 依赖于实现(Windows 上 16 位宽的字符,Linux 上 32 位宽),因此它是 i>不够。

标签: c++ character-encoding wstring


【解决方案1】:

更简单的版本。 基于 Marcelo Cantos Getting the actual length of a UTF-8 encoded std::string? 提供的解决方案

std::string substr(std::string originalString, int maxLength)
{
    std::string resultString = originalString;

    int len = 0;
    int byteCount = 0;

    const char* aStr = originalString.c_str();

    while(*aStr)
    {
        if( (*aStr & 0xc0) != 0x80 )
            len += 1;

        if(len>maxLength)
        {
            resultString = resultString.substr(0, byteCount);
            break;
        }
        byteCount++;
        aStr++;
    }

    return resultString;
}

【讨论】:

  • 不幸的是,这个解决方案只是部分正确。如果您查看我的回答,您会发现您成功地处理了 1.(不是在代码点中间切割)但是您在 2.(没有将代码点与其变音符号分开)和 3.(没有切割)失败了在语义字符的中间,例如西班牙语中的 LL)。后两种情况当然比较少见,但是......好吧,正确处理边缘情况是必要的。
【解决方案2】:

std::string 对象不是字符串,而是字节字符串。它根本不知道所谓的“encoding”。 std::wstring 也一样,只是它是一个 16 位值的字符串。

为了对需要寻址不同字符的文本执行操作(例如,当您想要获取子字符串时),您需要知道 std::string 对象使用什么编码。

更新: 既然您已经澄清了您的输入字符串是 UTF-8 编码的,那么您仍然需要决定用于输出 std::wstring 的编码。我想到了 UTF-16,但这实际上取决于您将传递 std::wstring 对象所期望的 API。假设 UTF-16 是可以接受的,您有多种选择:

  1. 在Windows上,您可以使用MultiByteToWideChar函数;不需要额外的依赖项。
  2. UTF8-CPP 库声称提供了一种轻量级的解决方案来处理 UTF-* 编码的字符串。我自己从未尝试过,但我一直听到关于它的好消息。
  3. 在 Linux 系统上,使用 libiconv 库非常普遍。
  4. 如果您需要处理各种疯狂的编码,并且想要完整的 alpha 和 omega 字,就编码而言,请查看 ICU

【讨论】:

  • std::wstringwchar_t 的字符串,可能是 16 位,是 32 位。
  • 我知道这一点,而且我知道我的编码。问题是如何使用该编码进行转换。
  • @W.Goeman:我现在更新了我的答案,提出了一些如何将 UTF-8 转换为其他编码的建议(即使使用 std::wstring,您仍然需要下定决心使用什么编码) .
【解决方案3】:

实际上只有两种可能的解决方案。如果你这样做 很多,在很远的距离,你最好转换你的 使用wchar_t(或int32_t, 或任何最合适的。这不是一个简单的副本,它 会将每个人 char 转换为目标类型,但是一个真正的 转换函数,它将识别多字节字符,以及 将它们转换为单个元素。

对于偶尔使用或更短的序列,可以编写自己的 用于推进n 字节的函数。对于 UTF-8,我使用以下内容:

inline size_t
size(
    Byte                ch )
{
    return byteCountTable[ ch ] ;
}

template< typename InputIterator >
InputIterator
succ(
    InputIterator       begin,
    size_t              size,
    std::random_access_iterator_tag )
{
    return begin + size ;
}

template< typename InputIterator >
InputIterator
succ(
    InputIterator       begin,
    size_t              size,
    std::input_iterator_tag )
{
    while ( size != 0 ) {
        ++ begin ;
        -- size ;
    }
    return begin ;
}

template< typename InputIterator >
InputIterator
succ(
    InputIterator       begin,
    InputIterator       end )
{
    if ( begin != end ) {
        begin = succ( begin, end, size( *begin ),
                      std::::iterator_traits< InputIterator >::iterator_category() ) ;
    }
    return begin ;
}

template< typename InputIterator >
size_t
characterCount(
    InputIterator       begin,
    InputIterator       end )
{
    size_t              result = 0 ;
    while ( begin != end ) {
        ++ result ;
        begin = succ( begin, end ) ;
    }
    return result ;
}

【讨论】:

  • 应该注意wchar_t 在许多平台上只有16 位,因此它不能仅在一个元素中表示许多代码点。相反,char32_t 是 C++11 提供的一种类型,它是固定宽度的,足以在单个元素中表示所有 Unicode。
【解决方案4】:

Unicode 很难。

  1. std::wstring 不是代码点列表,它是wchar_t 的列表,它们的宽度是实现定义的(VC++ 通常为 16 位,gcc 和 clang 为 32 位)。是的,这意味着它对可移植代码毫无用处...
  2. 单个字符可以在多个代码点上编码(因为diacritics
  3. 在某些语言中,两个不同的字符一起构成一个不可真正分离的“单元”(例如,LL 在西班牙语中被视为一个字母)。

所以……有点难。

解决 3) 可能代价高昂(需要特定的语言/用法注释);解决 1) 和 2) 是绝对必要的……并且需要支持 Unicode 的库或自己编写代码(并且可能会出错)。

  • 1) 很容易解决:编写从 UTF-8 转换为 CodePoint 的例程很简单(CodePoint 可以用 uint32_t 表示)
  • 2) 更难,它需要一个变音符号列表,并且子例程必须知道永远不要在变音符号之前剪切(它们遵循他们限定的字符)

否则,ICU 中可能有您要查找的内容。祝你好运。

【讨论】:

  • 如果您的语言带有一个体面的标准库,Unicode 并不难。 C++ 没有这样的。此外,LL 不是西班牙语中的字母;由于皇家学院的一些愚蠢行为,它曾经被认为是这样的,但他们最终承认这是一个有向图,并且在相当长的一段时间内,它在学术界、教科书、西班牙语语言环境等中都没有被认为是一封信。造成混淆的原因是西班牙字母与音素非常接近,而 LL 和 CH 被用来表示不同的音素。
  • @MiguelPérez:很高兴知道,几年前我学过西班牙语,字典让我很困惑,因为那很奇怪。不幸的是,这不是唯一发生这种情况的语言;)
  • 变音符号(他们跟随他们符合条件的字符)”这个的正确名称是什么?受虐狂?
  • @curiousguy:不管是后面还是前面,我觉得都不会更好。它确实避免了可能性的组合爆炸......并使我们的生活变得非常困难:(
  • 我的意思是:加上前缀,你是不是读了一个变音符号,你知道它后面必须跟着另一个代码点,你必须再消耗一个代码点。但是使用后缀,如果你读一封信,你什么都不知道。它可以是一个单独的字母,也可以与以下变音符号结合使用:你必须查看下一个代码点,以防万一(你怎么能在阻塞流的情况下做到这一点?)。
【解决方案5】:

为了简单起见,我假设您的编码是 UTF-8。在这种情况下,我们会有一些字符占用一个以上的字节,就像你的情况一样。 然后你有 std::string,这些 UTF-8 编码的字符被存储在其中。 现在你想根据字符而不是字节来 substr()。 我会编写一个将字符长度转换为字节长度的函数。对于 utf 8 情况,它看起来像:

#define UTF8_CHAR_LEN( byte ) (( 0xE5000000 >> (( byte >> 3 ) & 0x1e )) & 3 ) + 1

int32 GetByteCountForCharCount(const char* utf8Str, int charCnt)
{
    int ByteCount = 0;
    for (int i = 0; i < charCnt; i++)
    {
        int charlen = UTF8_CHAR_LEN(*utf8Str);
        ByteCount += charlen;
        utf8Str += charlen;
    }
    return ByteCount;
}

所以,假设你想 substr() 来自第 7 个字符的字符串。没问题:

int32 pos = GetByteCountForCharCount(str.c_str(), 7);
str.substr(pos); 

【讨论】:

  • 为什么使用宏而不是内联函数?为什么哦为什么要传递char const* 而不是std::string const&amp;。请在 C++ 问题中使用 C++ 习语。
【解决方案6】:

基于this我已经编写了我的utf8子字符串函数:

void utf8substr(std::string originalString, int SubStrLength, std::string& csSubstring)
{
    int len = 0, byteIndex = 0;
    const char* aStr = originalString.c_str();
    size_t origSize = originalString.size();

    for (byteIndex=0; byteIndex < origSize; byteIndex++)
    {
        if((aStr[byteIndex] & 0xc0) != 0x80)
            len += 1;

        if(len >= SubStrLength)
            break;
    }

    csSubstring = originalString.substr(0, byteIndex);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-24
    • 2016-08-08
    • 1970-01-01
    • 1970-01-01
    • 2021-01-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多