Получить фактическую длину в кодировке UTF-8 std :: string? - PullRequest
28 голосов
/ 31 октября 2010

мой 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?Спасибо

Ответы [ 10 ]

58 голосов
/ 31 октября 2010

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

int len = 0;
while (*s) len += (*s++ & 0xc0) != 0x80;
18 голосов
/ 17 сентября 2013

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

Действительно, стандартная библиотека действительно подтверждает существованиекодировки символов в форме локалей.Если ваша система поддерживает локаль, очень просто использовать стандартную библиотеку для вычисления длины строки.В приведенном ниже примере кода я предполагаю, что ваша система поддерживает локаль en_US.UTF-8.Если я скомпилирую код и выполню его как «./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.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; 
}
4 голосов
/ 31 октября 2010

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

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;
}
4 голосов
/ 31 октября 2010

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

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

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

2 голосов
/ 31 октября 2010

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

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

(Код с макушки головы).

1 голос
/ 31 октября 2010

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

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

0 голосов
/ 24 января 2019

Немного ленивый подход - подсчитывать только ведущие байты, но посещать каждый байт. Это экономит сложность декодирования различных размеров начальных байтов, но очевидно, что вы платите за посещение всех байтов, хотя обычно их не так много (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 битов, необходимых для расширенного юникода, но тогда другой подход не будет знать, как обращаться с этим кодом, так или иначе. *

0 голосов
/ 09 января 2019

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

Этот код я портирую с 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");
}
0 голосов
/ 31 октября 2010

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...