Ответ 1
Один из проектов, в которые я вношу свой вклад, имеет небольшую функцию, которая делает это:
Найдите Utf8StringSize
. Это зависит от другой крошечной функции в том же заголовочном файле.
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? Благодаря
Один из проектов, в которые я вношу свой вклад, имеет небольшую функцию, которая делает это:
Найдите Utf8StringSize
. Это зависит от другой крошечной функции в том же заголовочном файле.
Подсчитайте все первые байты (те, которые не соответствуют 10xxxxxx).
int len = 0;
while (*s) len += (*s++ & 0xc0) != 0x80;
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;
}
Вероятно, вам следует обратиться за советом к 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. Или еще лучше, просто используйте ответ Марсело Кантоса для подсчета первых байтов.
Это наивная реализация, но вам должно быть полезно посмотреть, как это делается:
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;
}
Я рекомендую вам использовать UTF8-CPP. Это библиотека только для заголовков для работы с UTF-8 на С++. С этим lib он будет выглядеть примерно так:
int LenghtOfUtf8String( const std::string &utf8_string )
{
return utf8::distance( utf8_string.begin(), utf8_string.end() );
}
(Код сверху.)
попробуйте использовать библиотеку кодирования, например iconv. он, вероятно, получил api, который вы хотите.
альтернативой является реализация собственного utf8strlen, который определяет длину каждого кода и итерацию кодовых точек вместо символов.
Библиотека 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);
Этот код я переношу с 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");
}
Просто еще одна наивная реализация для подсчета символов в строке 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;
}
Немного ленивый подход - подсчитывать только ведущие байты, но посещать каждый байт. Это экономит сложность декодирования различных размеров начальных байтов, но очевидно, что вы платите за посещение всех байтов, хотя обычно их не так много (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 битов, необходимых для расширенного юникода, но тогда другой подход не будет знать, как обращаться с этим кодом, так или иначе.