获取UTF-8编码的std :: string的实际长度?

jma*_*erx 32 c++ algorithm

我的std :: string是utf-8编码所以很明显,str.length()返回错误的结果.

我发现了这些信息,但我不确定如何使用它来执行此操作:

以下字节序列用于表示字符.要使用的序列取决于字符的UCS代码编号:

   0x00000000 - 0x0000007F:
       0xxxxxxx

   0x00000080 - 0x000007FF:
       110xxxxx 10xxxxxx

   0x00000800 - 0x0000FFFF:
       1110xxxx 10xxxxxx 10xxxxxx

   0x00010000 - 0x001FFFFF:
       11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
Run Code Online (Sandbox Code Playgroud)

如何找到UTF-8编码的std :: string的实际长度?谢谢

Mar*_*tos 63

计算所有第一个字节(与10xxxxxx不匹配的字节).

int len = 0;
while (*s) len += (*s++ & 0xc0) != 0x80;
Run Code Online (Sandbox Code Playgroud)

  • 你需要在`&`部分周围加括号. (4认同)
  • 请注意,这会返回代码点的数量 - 假设这表示“实际长度”。另外:仅对已验证为有效 UTF-8 序列的字符串使用此算法,因为它不会考虑可能导致错误结果的无效字节序列。 (4认同)
  • 这将是代码点的长度,而不是字形的长度。对于估计屏幕空间,即使使用固定长度的字体,这也不够。此外,它无法计算相应的 UTF-16 缓冲区的大小,尽管只要您停留在基本多语言平面中,它就可以工作(但要注意那些讨厌的表情符号)。 (2认同)
  • 对于现在及以后尝试在命令行中对齐项目(使用等宽字体)的人来说,这对于 unicode 范围 1F300–1F5FF 中的符号无法正常工作,因为它们被打印为 2 个字符长的符号 (2认同)

use*_*185 20

C++对编码一无所知,因此您不能指望使用标准函数来执行此操作.

标准库确实承认字符编码的存在,在语言环境的形式.如果您的系统支持语言环境,则可以非常轻松地使用标准库来计算字符串的长度.在下面的示例代码中,我假设您的系统支持语言环境en_US.UTF-8.如果我编译代码并将其作为"./a.outソニーSony"执行,则输出结果是有13个char值和7个字符.所有这些都没有提及UTF-8字符代码的内部表示或必须使用第三方库.

#include <clocale>
#include <cstdlib>
#include <iostream>
#include <string>

using namespace std;

int main(int argc, char *argv[])
{
  string str(argv[1]);
  unsigned int strLen = str.length();
  cout << "Length (char-values): " << strLen << '\n';
  setlocale(LC_ALL, "en_US.utf8");
  unsigned int u = 0;
  const char *c_str = str.c_str();
  unsigned int charCount = 0;
  while(u < strLen)
  {
    u += mblen(&c_str[u], strLen - u);
    charCount += 1;
  }
  cout << "Length (characters): " << charCount << endl; 
}
Run Code Online (Sandbox Code Playgroud)

  • 对,如果你想安全(ish),请使用 std::mbrlen (2认同)

小智 8

这是一个幼稚的实现,但了解它是如何完成的应该对您有所帮助:

std::size_t utf8_length(std::string const &s) {
  std::size_t len = 0;
  std::string::const_iterator begin = s.begin(), end = s.end();
  while (begin != end) {
    unsigned char c = *begin;
    int n;
    if      ((c & 0x80) == 0)    n = 1;
    else if ((c & 0xE0) == 0xC0) n = 2;
    else if ((c & 0xF0) == 0xE0) n = 3;
    else if ((c & 0xF8) == 0xF0) n = 4;
    else throw std::runtime_error("utf8_length: invalid UTF-8");

    if (end - begin < n) {
      throw std::runtime_error("utf8_length: string too short");
    }
    for (int i = 1; i < n; ++i) {
      if ((begin[i] & 0xC0) != 0x80) {
        throw std::runtime_error("utf8_length: expected continuation byte");
      }
    }
    len += n;
    begin += n;
  }
  return len;
}
Run Code Online (Sandbox Code Playgroud)


Cha*_*via 5

您可能应该采纳Omry的建议,并为此寻找一个专门的库。就是说,如果您只是想了解执行此操作的算法,请在下面发布。

基本上,您可以将字符串转换为宽元素格式,例如wchar_t。请注意,这wchar_t存在一些可移植性问题,因为wchar_t其大小因平台而异。在Windows上wchar_t为2个字节,因此非常适合表示UTF-16。但是在UNIX / Linux上,它是四个字节,因此用于表示UTF-32。因此,对于Windows,仅当您不包括0xFFFF以上的任何Unicode代码点时,此方法才有效。对于Linux,您可以在中包含整个代码点范围wchar_t。(幸运的是,使用C ++ 0x Unicode字符类型可以缓解此问题。)

注意了这一警告之后,您可以使用以下算法创建转换函数:

template <class OutputIterator>
inline OutputIterator convert(const unsigned char* it, const unsigned char* end, OutputIterator out) 
{
    while (it != end) 
    {
        if (*it < 192) *out++ = *it++; // single byte character
        else if (*it < 224 && it + 1 < end && *(it+1) > 127) { 
            // double byte character
            *out++ = ((*it & 0x1F) << 6) | (*(it+1) & 0x3F);
            it += 2;
        }
        else if (*it < 240 && it + 2 < end && *(it+1) > 127 && *(it+2) > 127) { 
            // triple byte character
            *out++ = ((*it & 0x0F) << 12) | ((*(it+1) & 0x3F) << 6) | (*(it+2) & 0x3F);
            it += 3;
        }
        else if (*it < 248 && it + 3 < end && *(it+1) > 127 && *(it+2) > 127 && *(it+3) > 127) { 
            // 4-byte character
            *out++ = ((*it & 0x07) << 18) | ((*(it+1) & 0x3F) << 12) |
                ((*(it+2) & 0x3F) << 6) | (*(it+3) & 0x3F);
            it += 4;
        }
        else ++it; // Invalid byte sequence (throw an exception here if you want)
    }

    return out;
}

int main()
{
    std::string s = "\u00EAtre";
    cout << s.length() << endl;

    std::wstring output;
    convert(reinterpret_cast<const unsigned char*> (s.c_str()), 
        reinterpret_cast<const unsigned char*>(s.c_str()) + s.length(), std::back_inserter(output));

    cout << output.length() << endl; // Actual length
}
Run Code Online (Sandbox Code Playgroud)

该算法不是完全通用的,因为InputIterator需要为无符号字符,因此您可以将每个字节解释为具有0到0xFF之间的值。OutputIterator是通用的(只是您可以使用std :: back_inserter而不用担心内存分配),但是它用作通用参数是有限的:基本上,它必须输出到足够大的元素数组来表示一个UTF-16或UTF-32字符,例如wchar_tuint32_t或的C ++ 0x char32_t类型。另外,我没有包含转换大于4个字节的字符字节序列的代码,但是您应该从发布的内容中了解算法的工作原理。

另外,如果只想计算字符数,而不是输出到新的宽字符缓冲区,则可以修改算法以包括计数器而不是OutputIterator。或更妙的是,只需使用Marcelo Cantos的答案来计算首字节。


Omr*_*dan 1

尝试使用像iconv这样的编码库。它可能有你想要的api。

另一种方法是实现您自己的 utf8strlen ,它确定每个代码点的长度并迭代代码点而不是字符。