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

时间:2010-10-31 12:51:36

标签: c++ algorithm

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

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

  

以下字节序列是   曾经代表一个角色。该   顺序是          used取决于字符的UCS代码:

   0x00000000 - 0x0000007F:
       0xxxxxxx

   0x00000080 - 0x000007FF:
       110xxxxx 10xxxxxx

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

   0x00010000 - 0x001FFFFF:
       11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

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

12 个答案:

答案 0 :(得分:56)

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

int len = 0;
while (*s) len += (*s++ & 0xc0) != 0x80;

答案 1 :(得分:18)

  

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.UTF-8");
  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; 
}

答案 2 :(得分:9)

我参与的其中一个项目有一个小功能,可以做到这一点:

http://openlierox.git.sourceforge.net/git/gitweb.cgi?p=openlierox/openlierox;a=blob;f=include/Unicode.h;h=a523b464fc65a7ad875e683cd830b41c9a01934a;hb=HEAD

寻找Utf8StringSize。它取决于同一头文件中的另一个微小功能。

答案 3 :(得分:4)

你应该接受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
}

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

此外,如果您只想计算字符数,而不是输出到新的宽字符缓冲区,则可以修改算法以包含计数器而不是OutputIterator。或者更好的是,只需使用Marcelo Cantos' answer来计算第一个字节。

答案 4 :(得分:4)

这是一个天真的实现,但它应该有助于您了解如何完成此操作:

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;
}

答案 5 :(得分:2)

我建议您使用UTF8-CPP。它是一个仅用于在C ++中使用UTF-8的头文件库。使用这个lib,它看起来像这样:

int LenghtOfUtf8String( const std::string &utf8_string ) 
{
    return utf8::distance( utf8_string.begin(), utf8_string.end() ); 
}

(代码来自我的头脑。)

答案 6 :(得分:1)

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

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

答案 7 :(得分:1)

我的大多数个人C库代码都只经过了英语的真正测试,但这是我实现utf-8字符串长度函数的方式。我最初基于this wiki page table中描述的位模式。现在这不是最易读的代码,但是我的编译器更喜欢benchmark。同样为此感到遗憾的是C代码,它应该可以很容易地转换为C ++中的std :: string,尽管稍作修改:)。

size_t utf8len(const char* const str)
{
    size_t len = 0;
    unsigned char c = str[0];
    for (size_t i = 1; c != 0; ++len, ++i)
    {
        if ((c & 0x80))
        {
            if (c < 0xC0)   // Invalid increment
                return 0;
            c >>= 4;
            if (c == 12)
                c++;
            i += c - 12;
        }
        c = str[i];
    }
    return len;
}

请注意,这不会验证任何字节(非常类似于此处所有其他建议的答案)。我个人将字符串验证与我的字符串长度函数分开,因为这不是责任。如果我们要将字符串验证移至另一个函数,则可以使验证完成如下操作。

bool utf8valid(const char* const str)
{
    if (str == NULL)
        return false;
    unsigned char c = str[0];
    for (size_t i = 1, inc = 0; c != 0; ++i)
    {
        if (inc > 1)
        {
            if ((c & 0xC0) != 0x80)
                return false;
            inc--;
        }
        else
        {
            inc = 1;
            if ((c & 0x80))
            {
                if (c < 0xC0 || c >= 0xF8)
                    return false;
                c >>= 4;
                if (c == 12)
                    c++;
                inc += c - 12;
            }
        }
        c = str[i];
    }
    return true;
}

如果您出于可读性考虑,我会承认其他建议更具可读性哈哈!

答案 8 :(得分:0)

UTF-8 CPP库有一个功能就是这样。您可以将库包含到项目中(它很小)或只是查看函数。 http://utfcpp.sourceforge.net/

char* twochars = "\xe6\x97\xa5\xd1\x88";
size_t dist = utf8::distance(twochars, twochars + 5);
assert (dist == 2);

答案 9 :(得分:0)

这段代码我从php-iconv移植到c ++,你需要先使用iconv,希望有用:

// porting from PHP
// http://lxr.php.net/xref/PHP_5_4/ext/iconv/iconv.c#_php_iconv_strlen
#define GENERIC_SUPERSET_NBYTES 4
#define GENERIC_SUPERSET_NAME   "UCS-4LE"

UInt32 iconvStrlen(const char *str, size_t nbytes, const char* encode)
{
    UInt32 retVal = (unsigned int)-1;

    unsigned int cnt = 0;

    iconv_t cd = iconv_open(GENERIC_SUPERSET_NAME, encode);
    if (cd == (iconv_t)(-1))
        return retVal;

    const char* in;
    size_t  inLeft;

    char *out;
    size_t outLeft;

    char buf[GENERIC_SUPERSET_NBYTES * 2] = {0};

    for (in = str, inLeft = nbytes, cnt = 0; inLeft > 0; cnt += 2) 
    {
        size_t prev_in_left;
        out = buf;
        outLeft = sizeof(buf);

        prev_in_left = inLeft;

        if (iconv(cd, &in, &inLeft, (char **) &out, &outLeft) == (size_t)-1) {
            if (prev_in_left == inLeft) {
                break;
            }
        }
    }
    iconv_close(cd);

    if (outLeft > 0)
        cnt -= outLeft / GENERIC_SUPERSET_NBYTES;

    retVal = cnt;
    return retVal;
}

UInt32 utf8StrLen(const std::string& src)
{
    return iconvStrlen(src.c_str(), src.length(), "UTF-8");
}

答案 10 :(得分:0)

这是另一种幼稚的实现,可以计算UTF-8字符串中的字符数

int utf8_strlen(const string& str)
{
    int c,i,ix,q;
    for (q=0, i=0, ix=str.length(); i < ix; i++, q++)
    {
        c = (unsigned char) str[i];
        if      (c>=0   && c<=127) i+=0;
        else if ((c & 0xE0) == 0xC0) i+=1;
        else if ((c & 0xF0) == 0xE0) i+=2;
        else if ((c & 0xF8) == 0xF0) i+=3;
        //else if (($c & 0xFC) == 0xF8) i+=4; // 111110bb //byte 5, unnecessary in 4 byte UTF-8
        //else if (($c & 0xFE) == 0xFC) i+=5; // 1111110b //byte 6, unnecessary in 4 byte UTF-8
        else return 0;//invalid utf8
    }
    return q;
}

答案 11 :(得分:0)

一种稍微懒惰的方法是只计算前导字节,但是访问每个字节。这样可以节省解码各种前导字节大小的复杂性,但显然您要付费访问所有字节,尽管通常没有那么多(2x-3x):

EventRepository > findById()

请注意,某些代码值作为前导字节是非法的,例如,那些表示比扩展​​unicode所需的20位更大的值,但其他方法无论如何都不知道如何处理该代码。