Подтвердить что ты не робот

Получение фактической длины кодированного UTF-8 std::string?

my 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

Как я могу найти фактическую длину кодированного UTF-8 std::string? Благодаря

4b9b3361

Ответ 2

Подсчитайте все первые байты (те, которые не соответствуют 10xxxxxx).

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

Ответ 3

  C++ ничего не знает о кодировках, поэтому вы не можете ожидать использования стандартная функция для этого.

Стандартная библиотека действительно признает существование кодировки символов в форме локалей. Если ваша система поддерживает локаль, очень просто использовать стандартную библиотеку для вычисления длины строки. В приведенном ниже примере кода я предполагаю, что ваша система поддерживает локаль en_US.utf8. Если я скомпилирую код и выполню его как "./a.out ー ニ ー Sony", то получится, что в нем было 13 символов и 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; 
}

Ответ 4

Вероятно, вам следует обратиться за советом к Omry и заглянуть в специализированную библиотеку. Тем не менее, если вы просто хотите понять алгоритм для этого, я отправлю его ниже.

В принципе, вы можете преобразовать свою строку в формат более широкого элемента, например wchar_t. Обратите внимание, что wchar_t имеет несколько проблем с переносимостью, поскольку wchar_t имеет разный размер в зависимости от вашей платформы. В Windows wchar_t имеет 2 байта и поэтому идеально подходит для представления UTF-16. Но в UNIX/Linux он имеет четыре байта и поэтому используется для представления UTF-32. Поэтому для Windows это будет работать только в том случае, если вы не включили кодовые обозначения Unicode выше 0xFFFF. Для Linux вы можете включить весь диапазон кодовых точек в wchar_t. (К счастью, эта проблема будет смягчена символами символов С++ 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 должен быть беззнаковым char, поэтому вы можете интерпретировать каждый байт как значение от 0 до 0xFF. Вывод OutputIterator является общим (просто чтобы вы могли использовать std:: back_inserter и не беспокоиться о распределении памяти), но его использование в качестве общего параметра ограничено: в основном оно должно выводиться в массив элементов, достаточно больших для представления UTF-16 или UTF-32, например wchar_t, uint32_t или типы С++ 0x char32_t. Кроме того, я не включил код для преобразования последовательностей символов байта более 4 байтов, но вы должны понять, как работает алгоритм из того, что было опубликовано.

Кроме того, если вы хотите просто подсчитать количество символов, а не выводить их в новый широкосимвольный буфер, вы можете изменить алгоритм на включение счетчика, а не OutputIterator. Или еще лучше, просто используйте ответ Марсело Кантоса для подсчета первых байтов.

Ответ 5

Это наивная реализация, но вам должно быть полезно посмотреть, как это делается:

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

Ответ 6

Я рекомендую вам использовать UTF8-CPP. Это библиотека только для заголовков для работы с UTF-8 на С++. С этим lib он будет выглядеть примерно так:

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

(Код сверху.)

Ответ 7

попробуйте использовать библиотеку кодирования, например iconv. он, вероятно, получил api, который вы хотите.

альтернативой является реализация собственного utf8strlen, который определяет длину каждого кода и итерацию кодовых точек вместо символов.

Ответ 8

Библиотека CPP UTF-8 имеет функцию, которая делает именно это. Вы можете включить библиотеку в свой проект (она небольшая) или просто посмотреть на функцию. http://utfcpp.sourceforge.net/

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

Ответ 9

Этот код я переношу с php-iconv на С++, вам нужно сначала использовать 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

Просто еще одна наивная реализация для подсчета символов в строке 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

Немного ленивый подход - подсчитывать только ведущие байты, но посещать каждый байт. Это экономит сложность декодирования различных размеров начальных байтов, но очевидно, что вы платите за посещение всех байтов, хотя обычно их не так много (2x-3x):

size_t utf8Len(std::string s)
{
  return std::count_if(s.begin(), s.end(),
    [](char c) { (static_cast<unsigned char>(c) & 0xC0) != 0x80; } );
}

Обратите внимание, что некоторые значения кода являются недопустимыми в качестве начальных байтов, например, те, которые представляют большие значения, чем 20 битов, необходимых для расширенного юникода, но тогда другой подход не будет знать, как обращаться с этим кодом, так или иначе.