Jak liczyć znaki w ciągu unicode w C

Powiedzmy, że mam ciąg znaków:

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

Biorąc pod uwagę, że moje kodowanie to utf-8, łańcuch ten ma długość 12 bajtów (trzy znaki hanzi to trzy bajty każdy, łaciński znak z makronem to dwa bajty, a 'a' to jeden bajt:

strlen(theString) == 12

Jak policzyć ilość znaków? Jak mogę zrobić odpowiednik subscription tak, że:

theString[3] == "好"

Jak mogę pokroić i kotować takie sznurki?

Author: Tom Zych, 2011-09-04

10 answers

Liczysz tylko te znaki, które mają dwa górne bity, nie są ustawione na 10 (tzn. wszystko mniej niż 0x80 lub większe niż 0xbf).

To dlatego, że wszystkie znaki z dwoma pierwszymi bitami ustawionymi na 10 są ciągłymi bajtami UTF-8.

Zobacz tutaj aby uzyskać opis kodowania i jak strlen może pracować na łańcuchu UTF-8.

Do krojenia i krojenia strun UTF-8, zasadniczo musisz przestrzegać tych samych zasad. Każdy bajt zaczynający się od bitu 0 lub Sekwencja 11 jest początkiem punktu kodu UTF-8, wszystkie pozostałe są znakami kontynuacji.

Najlepszym rozwiązaniem, jeśli nie chcesz korzystać z biblioteki innej firmy, jest po prostu dostarczenie funkcji w następujący sposób:

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;

Aby uzyskać, odpowiednio:

  • lewy sz UTF-8 bajtów łańcucha.
  • sz UTF-8 bajtów łańcucha, zaczynającego się od pos.
  • reszta UTF-8 bajtów łańcucha, zaczynająca się od pos.

To będzie przyzwoity budulec, aby móc manipulować strunami wystarczająco do swoich celów.

 27
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:16:59

Najprostszym sposobem jest użycie biblioteki typu ICU

 17
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 08:27:25

Spróbuj tego rozmiaru:

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

Przykładowy przebieg:

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

Zauważ, że twój przykład ma wyłączony przez jeden błąd. theString[2] == "好"

 13
Author: Matt Joiner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-18 11:17:21

W zależności od Twojego pojęcia "charakter", to pytanie może być mniej lub bardziej zaangażowane.

Po pierwsze, powinieneś przekształcić łańcuch bajtów w ciąg znaków kodowych unicode. Możesz to zrobić z iconv() OIOM, choć jeśli jest to jedyna rzecz, którą robisz, iconv() jest o wiele łatwiejsze i jest częścią POSIX.

Twój ciąg znaków kodowych unicode może być czymś w rodzaju zakończonej znakiem null uint32_t[], lub jeśli masz C1x, tablicą char32_t. Wielkości tej tablicy (tj. jej liczby elementów, Nie jego rozmiar w bajtach) to liczba punktów kodowych (plus terminator), a to powinno dać bardzo dobry początek.

Jednak pojęcie "drukowanego znaku" jest dość złożone i możesz preferować liczyć grafemy zamiast punktów kodowych - na przykład a z akcentem ^ mogą być wyrażone jako dwa punkty kodowe unicode lub jako połączony starszy punkt kodowy â - oba są ważne i oba są wymagane przez standard unicode, aby były traktowane jednakowo. Jest proces zwany "normalizacją", który zamienia Twój ciąg znaków w określoną wersję, ale istnieje wiele grafemów, które nie są expressible jako pojedynczy punkt kodowy i ogólnie nie ma możliwości obejścia odpowiedniej biblioteki, która to rozumie i liczy grafemy dla Ciebie.

To powiedziawszy, to do ciebie należy decyzja, jak skomplikowane są Twoje skrypty i jak dokładnie chcesz je traktować. Przekształcenie w Punkty kodowe unicode jest koniecznością, wszystko poza tym jest według twojego uznania.

Nie nie wahaj się zadawać pytań na temat OIOM, jeśli zdecydujesz, że tego potrzebujesz, ale najpierw poznaj znacznie prostsze iconv().

 8
Author: Kerrek SB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 10:27:39

W realnym świecie, {[0] } nie jest sensowną operacją. Dlaczego miałbyś chcieć zastąpić znak na określonej pozycji w łańcuchu innym znakiem? Z pewnością nie ma zadania przetwarzania tekstu w języku naturalnym, dla którego ta operacja ma znaczenie.

Liczenie znaków jest również mało prawdopodobne, aby miało znaczenie. Ile znaków (jak na twoje wyobrażenie "charakter") jest w "á"? Może "á"? A teraz "གི"? Jeśli potrzebujesz tych informacji do realizacji niektórych rodzaj edycji tekstu, będziesz musiał poradzić sobie z tymi trudnymi pytaniami, lub po prostu użyć istniejącego zestawu narzędzi biblioteki / gui. Polecam to drugie, chyba że jesteś ekspertem od skryptów i języków świata i myślisz, że możesz zrobić lepiej.

Dla wszystkich innych celów, strlen mówi dokładnie tę informację, która jest rzeczywiście przydatna: ile miejsca zajmuje ciąg znaków. To jest to, co jest potrzebne do łączenia i oddzielania ciągów. Jeśli wszystko, co chcesz zrobić, to połączyć ciągi lub oddzielić je w określonym ograniczniku, snprintf (lub strcat jeśli nalegasz...) i strstr są wszystkim, czego potrzebujesz.

Jeśli chcesz wykonywać operacje tekstu w języku naturalnym wyższego poziomu, takie jak pisanie wielkimi literami, łamanie linii itp. lub nawet operacje wyższego poziomu, takie jak pluralizacja, napięte zmiany itp. następnie będziesz potrzebować biblioteki, takiej jak OIOM lub odpowiednio czegoś znacznie wyższego poziomu i zdolnego językowo(i specyficznego dla języka / języków, z którymi pracujesz).

Ponownie, większość programów nie mieć jakiekolwiek zastosowanie do tego rodzaju rzeczy i po prostu trzeba zebrać i przeanalizować tekst bez żadnych względów do języka naturalnego.

 2
Author: R..,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 12:58:23
while (s[i]) {
    if ((s[i] & 0xC0) != 0x80)
        j++;
    i++;
}
return (j);

To zliczy znaki w łańcuchu UTF-8... (Znaleziono w tym artykule: jeszcze szybsze liczenie znaków UTF-8 )

Jednak wciąż jestem wciśnięty w krojenie i konkatenację?!?

 1
Author: jsj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 08:27:34

Ogólnie powinniśmy używać innego typu danych dla znaków unicode.

Na przykład można użyć typu danych wide char

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

zwróć uwagę na modyfikator L, który mówi, że łańcuch znaków składa się z szerokich znaków.

Długość tego ciągu można obliczyć za pomocą funkcji wcslen, która zachowuje się jak strlen.

 1
Author: abahgat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 08:35:47

Jedna rzecz, która nie jest jasna z powyższych odpowiedzi, to dlaczego nie jest to proste. Każdy znak jest kodowany w taki czy inny sposób - nie musi to być na przykład UTF-8 - i każdy znak może mieć wiele kodowań, z różnymi sposobami obsługi łączenia akcentów itp. Reguły są bardzo skomplikowane i różnią się w zależności od kodowania (np. utf-8 vs.utf-16).

To pytanie ma ogromne obawy dotyczące bezpieczeństwa, więc konieczne jest, aby było to zrobione poprawnie. Korzystać z biblioteki dostarczonej przez system operacyjny lub dobrze znana biblioteka innej firmy do manipulowania ciągami unicode; nie zwijaj własnych.

 1
Author: Steve Dispensa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 14:09:39

Podobne implementacje robiłem lata temu. Ale nie mam przy sobie kodu.

Dla każdego znaku unicode, pierwszy bajt opisuje liczbę bajtów po nim, aby zbudować znak unicode. Na podstawie pierwszego bajtu można określić długość każdego znaku unicode.

Myślę, że to dobra biblioteka UTF8. Wpisz tutaj Opis linku

 0
Author: Senthil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-06 17:36:09

Sekwencja punktów kodowych stanowi pojedynczą sylabę / literę / znak w wielu innych językach spoza Europy Zachodniej (np.]}

Tak więc, kiedy liczysz długość lub znajdujesz podłańcuch (są zdecydowanie przypadki znalezienia podłańcucha - powiedzmy granie w grę wisielca), musisz przesuwać sylabę po sylabie, a nie po punkcie kodu po punkcie kodu.

Więc definicja znaku / sylaby i gdzie rzeczywiście podzielić ciąg na "kawałki sylab" zależy od charakteru języka, z którym masz do czynienia. Na przykład wzór sylab w wielu językach indyjskich (Hindi, Telugu, Kannada, malajalam, nepalski, Tamilski, pendżabski itp.) może być dowolną z poniższych

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

Musisz przetworzyć łańcuch i poszukać powyższych wzorców, aby złamać łańcuch i znaleźć podłańcuchy.

Myślę, że nie jest możliwe, aby mieć metodę ogólnego przeznaczenia, która może magicznie złamać ciągi w powyższym moda dla dowolnego ciągu znaków unicode ( lub sekwencji punktów kodu) - ponieważ wzór, który działa dla jednego języka, może nie mieć zastosowania dla innej litery;

Myślę, że mogą istnieć pewne metody / biblioteki, które mogą przyjmować pewne definicje / parametry konfiguracyjne jako dane wejściowe, aby podzielić ciągi unicode na takie sylaby. Nie jestem pewien! Docenić, jeśli ktoś może podzielić się, jak rozwiązali ten problem za pomocą dostępnych komercyjnie lub otwartych metod źródłowych.

 -1
Author: SRKJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-20 02:41:39