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

Как считать символы в строке юникода в C

Скажем, у меня есть строка:

char theString[] = "你们好āa";

Учитывая, что моя кодировка является utf-8, эта строка имеет длину 12 байт (три символа hanzi имеют по три байта, латинский символ с макросом - два байта, а "a" - один байт:

strlen(theString) == 12

Как подсчитать количество символов? Как я могу сделать эквивалент подписки, чтобы:

theString[3] == "好"

Как я могу нарезать и cat такие строки?

4b9b3361

Ответ 1

Вы только считаете, что символы, у которых верхние два бита не установлены на 10 (т.е. все меньше 0x80 или больше, чем 0xbf).

Это потому, что все символы с двумя верхними битами, установленными в 10, являются байтами продолжения UTF-8.

Смотрите здесь для описания кодировки и как strlen может работать с строкой UTF-8.

Для нарезки и обрезания строк UTF-8 вы, в основном, должны следовать тем же правилам. Любой байт, начинающийся с бит 0 или 11, является началом кодовой точки UTF-8, все остальные являются символами продолжения.

Лучше всего, если вы не хотите использовать стороннюю библиотеку, просто предоставить функции по следующим направлениям:

utf8left (char *destbuff, char *srcbuff, size_t sz);
utf8mid  (char *destbuff, char *srcbuff, size_t pos, size_t sz);
utf8rest (char *destbuff, char *srcbuff, size_t pos;

чтобы получить соответственно:

  • левая строка sz UTF-8 строки.
  • байты sz UTF-8 строки, начиная с pos.
  • остальные байты UTF-8 строки, начиная с pos.

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

Ответ 2

Самый простой способ - использовать библиотеку, например ICU

Ответ 3

Попробуйте это для размера:

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// returns the number of utf8 code points in the buffer at s
size_t utf8len(char *s)
{
    size_t len = 0;
    for (; *s; ++s) if ((*s & 0xC0) != 0x80) ++len;
    return len;
}

// returns a pointer to the beginning of the pos'th utf8 codepoint
// in the buffer at s
char *utf8index(char *s, size_t pos)
{    
    ++pos;
    for (; *s; ++s) {
        if ((*s & 0xC0) != 0x80) --pos;
        if (pos == 0) return s;
    }
    return NULL;
}

// converts codepoint indexes start and end to byte offsets in the buffer at s
void utf8slice(char *s, ssize_t *start, ssize_t *end)
{
    char *p = utf8index(s, *start);
    *start = p ? p - s : -1;
    p = utf8index(s, *end);
    *end = p ? p - s : -1;
}

// appends the utf8 string at src to dest
char *utf8cat(char *dest, char *src)
{
    return strcat(dest, src);
}

// test program
int main(int argc, char **argv)
{
    // slurp all of stdin to p, with length len
    char *p = malloc(0);
    size_t len = 0;
    while (true) {
        p = realloc(p, len + 0x10000);
        ssize_t cnt = read(STDIN_FILENO, p + len, 0x10000);
        if (cnt == -1) {
            perror("read");
            abort();
        } else if (cnt == 0) {
            break;
        } else {
            len += cnt;
        }
    }

    // do some demo operations
    printf("utf8len=%zu\n", utf8len(p));
    ssize_t start = 2, end = 3;
    utf8slice(p, &start, &end);
    printf("utf8slice[2:3]=%.*s\n", end - start, p + start);
    start = 3; end = 4;
    utf8slice(p, &start, &end);
    printf("utf8slice[3:4]=%.*s\n", end - start, p + start);
    return 0;
}

Пример прогона:

[email protected]:~/Desktop$ echo -n 你们好āa | ./utf8ops 
utf8len=5
utf8slice[2:3]=好
utf8slice[3:4]=ā

Обратите внимание, что ваш пример отключен одной ошибкой. theString[2] == "好"

Ответ 4

В зависимости от вашего понятия "характер" этот вопрос может быть более или менее вовлечен.

Во-первых, вы должны преобразовать свою байтовую строку в строку кодов unicode. Вы можете сделать это с помощью iconv() ICU, но если это единственное, что вы делаете, iconv() намного проще, и это часть POSIX.

Ваша строка кодовых точек юникода может быть чем-то вроде uint32_t[] с нулевым завершением или если у вас есть C1x, массив char32_t. Размер этого массива (т.е. Его количество, а не его размер в байтах) - это количество кодовых точек (плюс терминатор), и это должно дать вам очень хорошее начало.

Однако понятие "печатаемый символ" довольно сложно, и вы можете предпочесть считать графемы, а не кодовые точки, например, a с акцентом ^ может быть выражен как два кодовых пункта unicode, или как комбинированный legacy codepoint â - оба действительны, и оба требуются стандартом Юникода для обработки одинаково. Существует процесс, называемый "нормализацией", который превращает вашу строку в определенную версию, но есть много графем, которые не могут быть выражены как единый код, и вообще нет никакой возможности вокруг соответствующей библиотеки, которая понимает это и считает графемы для вас.

Тем не менее, вам решать, насколько сложны ваши сценарии и насколько тщательно вы хотите их обработать. Преобразование в кодовые страницы unicode является обязательным, все, что за его пределами, зависит от вашего усмотрения.

Не стесняйтесь задавать вопросы о ICU, если вы решите, что вам это нужно, но не стесняйтесь сначала исследовать гораздо более простой iconv().

Ответ 5

В общем случае мы должны использовать другой тип данных для символов Юникода.

Например, вы можете использовать широкий тип данных char

wchar_t theString[] = L"你们好āa";

Обратите внимание на модификатор L, который сообщает, что строка состоит из широких символов.

Длина этой строки может быть рассчитана с помощью функции wcslen, которая ведет себя как strlen.

Ответ 6

В реальном мире theString[3]=foo; не является значимой операцией. Зачем вам когда-либо хотеть заменить персонажа в определенной позиции в строке другим персонажем? Конечно, нет задачи обработки текстового текста, для которой эта операция имеет смысл.

Подсчет символов также маловероятен. Сколько персонажей (для вашей идеи "характер" ) есть в "á"? Как насчет "á"? Теперь как насчет "གི"? Если вам нужна эта информация для реализации какого-либо редактирования текста, вам придется иметь дело с этими трудными вопросами или просто использовать существующий набор инструментов библиотеки /gui. Я бы порекомендовал последнего, если вы не специалист по мировым сценариям и языкам, и думайте, что можете сделать лучше.

Для всех других целей strlen сообщает вам именно ту часть информации, которая действительно полезна: сколько занимает пространство памяти, которое занимает строка. Это то, что необходимо для объединения и разделения строк. Если все, что вы хотите сделать, это комбинировать строки или разделить их на конкретном разделителе, snprintf (или strcat, если вы настаиваете...) и strstr - все, что вам нужно.

Если вы хотите выполнять операции на естественном языке с более высоким уровнем, например, капитализацию, разрывы строк и т.д. или даже более высокоуровневые операции, такие как плюрализация, изменение времени и т.д., тогда вам понадобится библиотека, подобная ICU или, соответственно, что-то гораздо более высокоуровневое и лингвистически-способное (и специфичное для языка (ов), с которым вы работаете).

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

Ответ 7

while (s[i]) {
    if ((s[i] & 0xC0) != 0x80)
        j++;
    i++;
}
return (j);

Это будет считать символы в строке UTF-8... (Найдено в этой статье: Еще более быстрый подсчет символов UTF-8)

Однако я все еще в тупике нарезать и конкатенировать?!?

Ответ 8

Одна вещь, непонятная из приведенных выше ответов, - почему это не просто. Каждый символ кодируется так или иначе - например, он не должен быть UTF-8, и каждый символ может иметь несколько кодировок с различными способами обработки сочетания акцентов и т.д. Правила действительно сложны и варьируются в зависимости от кодирования (например, utf-8 vs. utf-16).

Этот вопрос имеет огромные проблемы с безопасностью, поэтому необходимо, чтобы это было сделано правильно. Используйте библиотеку, поставляемую ОС, или известную стороннюю библиотеку для управления строками Unicode; не сворачивайте свои собственные.

Ответ 9

Я сделал аналогичные годы реализации. Но у меня нет кода со мной.

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

Я думаю, что это хорошая библиотека UTF8. введите ссылку здесь

Ответ 10

Последовательность кодовых точек составляет единый слог/букву/символ во многих других неевропейских языках (например, все языки с индексом)

Итак, когда вы подсчитываете длину ИЛИ находите подстроку (определенно используются случаи нахождения подстрок - скажем, играем в игру палача), вам нужно продвигать слог по слогу, а не по кодовой точке по кодовой точке.

Таким образом, определение символа/слога и где вы фактически разбиваете строку на "куски слогов", зависит от характера языка, с которым вы имеете дело. Например, образец слогов во многих языках индикаторов (хинди, телугу, каннада, малаялам, непальский, тамильский, панджаби и т.д.) Может быть любым из следующих

V  (Vowel in their primary form appearing at the beginning of the word)
C (consonant)
C + V (consonant + vowel in their secondary form)
C + C + V
C + C + C + V

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

Я не думаю, что можно использовать метод общего назначения, который может магически сломать строки вышеописанным способом для любой строки юникода (или последовательности кодовых точек) - поскольку шаблон, который работает для одного языка, может быть неприменим для другого письма;

Я предполагаю, что могут быть некоторые методы/библиотеки, которые могут принимать некоторые параметры определения/конфигурации в качестве входных данных, чтобы разбить строки юникода на такие слоги слога. Не уверен, хотя! Цените, если кто-то может поделиться тем, как они решили эту проблему, используя любые коммерчески доступные или с открытым исходным кодом методы.