So zählen Sie Zeichen in einer Unicode-Zeichenfolge in C

Lesezeit: 11 Minuten

Benutzeravatar von jsj
jsj

Nehmen wir an, ich habe eine Zeichenfolge:

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

Da meine Codierung utf-8 ist, ist diese Zeichenfolge 12 Bytes lang (die drei Hanzi-Zeichen sind jeweils drei Bytes, das lateinische Zeichen mit dem Makron ist zwei Bytes und das ‘a’ ist ein Byte:

strlen(theString) == 12

Wie kann ich die Anzahl der Zeichen zählen? Wie kann ich das Äquivalent zum Abonnieren tun, damit:

theString[3] == "好"

Wie kann ich solche Saiten schneiden und katz?

Benutzeravatar von paxdiablo
paxdiablo

Sie zählen nur die Zeichen, deren obere zwei Bits nicht gesetzt sind 10 (dh alles weniger als das 0x80 oder größer als 0xbf).

Das liegt daran, dass alle Zeichen mit den oberen beiden Bits auf gesetzt sind 10 sind UTF-8-Fortsetzungsbytes.

Siehe hier für eine Beschreibung der Kodierung und wie strlen kann mit einer UTF-8-Zeichenfolge arbeiten.

Beim Slicen und Dicing von UTF-8-Strings müssen Sie grundsätzlich die gleichen Regeln befolgen. Jedes Byte, das mit a beginnt 0 bisschen oder a 11 Sequenz ist der Beginn eines UTF-8-Codepunkts, alle anderen sind Fortsetzungszeichen.

Wenn Sie keine Bibliothek eines Drittanbieters verwenden möchten, ist es am besten, einfach Funktionen wie folgt bereitzustellen:

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;

jeweils zu bekommen:

  • die linke sz UTF-8 Bytes einer Zeichenfolge.
  • das sz UTF-8 Bytes einer Zeichenfolge, beginnend bei pos.
  • die restlichen UTF-8-Bytes einer Zeichenfolge, beginnend bei pos.

Dies ist ein anständiger Baustein, um die Saiten für Ihre Zwecke ausreichend manipulieren zu können.

  • Ja, es scheint, dass ich viel davon selbst implementieren muss. Ich habe es in der letzten Stunde geschafft, ein u_strlen und ein u_charAt zu implementieren. Auf dieser Grundlage sollte man Scheiben schneiden können.

    – jsj

    4. September 2011 um 9:47 Uhr

  • Akzeptiert, weil ich am Ende meine eigenen Funktionen geschrieben habe.

    – jsj

    4. September 2011 um 15:57 Uhr

  • Hinweis: Dies ignoriert die in beschriebenen Graphem-Cluster UAX#29dh “नि” soll als einzelne Texteinheit angesehen werden, ergibt jedoch mit der Methode in dieser Antwort eine Länge von 2.

    – AliciaBytes

    2. November 2016 um 20:42 Uhr


Benutzeravatar von Matt Joiner
Matt Tischler

Versuchen Sie dies für die Größe:

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

Beispiellauf:

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

Beachten Sie, dass Ihr Beispiel einen Off-by-One-Fehler aufweist. theString[2] == "好"

  • Kennen Sie zufällig eine Implementierung von strlen() zum Kombinieren von Zeichen? wie ‘a’ mit Akzent zum Beispiel sollte 1 zurückgeben, nicht 2

    – Nulik

    26. September 2016 um 16:38 Uhr

  • @Nulik: Das klingt utf8len, utf8len(“ā”) sollte 1 zurückgeben.

    – Matt Tischler

    27. September 2016 um 3:44 Uhr

  • Sind Sie sicher, dass das Beispiel in der Frage einen Off-by-One-Fehler aufweist?好 ist zwei Bytes lang, aber die Definition einer solchen Zeichenfolge fügt am Ende immer ein Nullzeichen hinzu, also ist 3 meiner Meinung nach richtig.

    – iFreilicht

    21. August 2020 um 13:06 Uhr

  • Deckt dieser Code alle gültigen UTF8 oder nur eine Teilmenge ab?

    Benutzer5125586

    9. Februar 2021 um 19:50 Uhr

  • @RichardMcFriendOluwamuyiwa Ich glaube, es sollte auf allen utf8 funktionieren

    – Matt Tischler

    11. Februar 2021 um 10:59 Uhr

Am einfachsten ist es, eine Bibliothek wie z Intensivstation

  • @Mark.. Ich habe ein paar Fragen zur Intensivstation gestellt. Die Leute antworteten meistens, dass es für einfache Operationen unnötig sei. stackoverflow.com/questions/7294447/…

    – jsj

    4. September 2011 um 8:29 Uhr

  • @ trideceth12: In vielen Fällen möchten Sie tatsächlich auf Graphem-Cluster zugreifen, nicht auf Zeichen. und die Implementierung von Grund auf ist weitaus aufwendiger als nur das Decodieren von UTF-8, daher könnte die Verwendung einer Bibliothek eine gute Idee sein

    – Christoph

    4. September 2011 um 9:05 Uhr

  • @Christoph: In der Tat! Und die ICU-Regex-Bibliothek unterstützt vollständige Unicode-erweiterte Graphem-Cluster über die \X, was diese Dinge einfach macht. Das heißt, es gibt Teile von C-Code, die alles für sich selbst erledigen, wie z vim – das scheint jedoch eher so etwas zu verwenden \PM\pM*, und funktioniert auch nur auf dem BMP. Seufzen.

    – tchrist

    6. September 2011 um 18:24 Uhr

Abhängig von Ihrer Vorstellung von “Charakter” kann diese Frage mehr oder weniger kompliziert werden.

Zunächst einmal sollten Sie Ihre Bytezeichenfolge in eine Zeichenfolge von Unicode-Codepunkten umwandeln. Sie können dies mit tun iconv() der Intensivstation, aber wenn dies das Einzige ist, was Sie tun, iconv() ist viel einfacher, und es ist Teil von POSIX.

Ihre Zeichenfolge von Unicode-Codepunkten könnte so etwas wie eine nullterminierte sein uint32_t[]oder wenn Sie C1x haben, ein Array von char32_t. Die Größe dieses Arrays (dh seine Anzahl von Elementen, nicht seine Größe in Bytes) ist die Anzahl von Codepunkten (plus Abschlusszeichen), und das sollte Ihnen einen sehr guten Start geben.

Der Begriff eines “druckbaren Zeichens” ist jedoch ziemlich komplex, und Sie ziehen es vielleicht vor, zu zählen Grapheme eher als Codepunkte – zum Beispiel an a mit Akzent ^ kann als zwei Unicode-Codepoints oder als kombinierter Legacy-Codepoint ausgedrückt werden â – beide sind gültig, und beide müssen vom Unicode-Standard gleich behandelt werden. Es gibt einen Prozess namens “Normalisierung”, der Ihre Zeichenfolge in eine bestimmte Version umwandelt, aber es gibt viele Grapheme, die nicht als einzelner Codepunkt ausgedrückt werden können, und im Allgemeinen führt kein Weg an einer geeigneten Bibliothek vorbei, die dies versteht und Grapheme für Sie zählt .

Allerdings liegt es an Ihnen zu entscheiden, wie komplex Ihre Skripte sind und wie gründlich Sie sie behandeln möchten. Die Umwandlung in Unicode-Codepoints ist ein Muss, alles darüber hinaus liegt in Ihrem Ermessen.

Zögern Sie nicht, Fragen zur Intensivstation zu stellen, wenn Sie entscheiden, dass Sie sie benötigen, aber fühlen Sie sich frei, die wesentlich einfacheren Möglichkeiten zu erkunden iconv() Erste.

R.. GitHub STOP HELPING ICEs Benutzeravatar
R.. GitHub HÖREN SIE AUF, ICE ZU HELFEN

In der echten Welt, theString[3]=foo; ist keine sinnvolle Operation. Warum möchten Sie jemals ein Zeichen an einer bestimmten Position in der Zeichenfolge durch ein anderes Zeichen ersetzen? Es gibt sicherlich keine Aufgabe zur Verarbeitung von Text in natürlicher Sprache, für die diese Operation sinnvoll ist.

Das Zählen von Zeichen ist ebenfalls unwahrscheinlich. Wie viele Zeichen (für Ihre Vorstellung von “Charakter”) gibt es in “á”? Wie wäre es mit einer”? Wie wäre es jetzt mit “གི”? Wenn Sie diese Informationen benötigen, um eine Art Textbearbeitung zu implementieren, müssen Sie sich mit diesen schwierigen Fragen auseinandersetzen oder einfach ein vorhandenes Bibliotheks-/GUI-Toolkit verwenden. Ich würde letzteres empfehlen, es sei denn, Sie sind ein Experte für Weltskripte und -sprachen und glauben, dass Sie es besser können.

Für alle anderen Zwecke strlen gibt Ihnen genau die Information, die wirklich nützlich ist: wie viel Speicherplatz ein String benötigt. Dies wird zum Kombinieren und Trennen von Zeichenfolgen benötigt. Wenn Sie nur Zeichenfolgen kombinieren oder an einem bestimmten Trennzeichen trennen möchten, snprintf (oder strcat wenn Sie darauf bestehen…) und strstr sind alles, was Sie brauchen.

Wenn Sie Textoperationen auf höherer Ebene wie Großschreibung, Zeilenumbruch usw. oder sogar Operationen auf höherer Ebene wie Pluralisierung, Tempusänderungen usw. ausführen möchten, benötigen Sie entweder eine Bibliothek wie ICU oder entsprechend etwas viel höherrangig und sprachlich fähig (und spezifisch für die Sprache(n), mit der/denen Sie arbeiten).

Auch hier haben die meisten Programme keine Verwendung für solche Dinge und müssen nur Text ohne Rücksicht auf natürliche Sprache zusammenstellen und analysieren.

  • @R Die Verwendung besteht darin, Pinyin in numerischer Form (ni2hao3ma5) in Pinyin mit Akzenten umzuwandeln. Ich habe jetzt meine eigenen Funktionen geschrieben, basierend auf der inhärenten Bedeutung im ersten Byte eines Unicode-Zeichenpunkts. Es ist ein bisschen klobig, aber es erledigt die Arbeit, ohne dass eine umfangreiche Bibliothek eingebunden werden muss.

    – jsj

    4. September 2011 um 15:56 Uhr

  • @ trideceth12: Ich habe das gleiche selbst gemacht. Es waren nur ein paar Zeilen Perl. Wirklich.

    – tchrist

    6. September 2011 um 18:26 Uhr

  • Ich würde argumentieren, dass Sie fast nie wissen wollen, wie viel “Speicher” vorhanden ist, und was Sie wirklich wollen, wenn Sie über Länge sprechen, sind “Zeichen”, nicht Bytes. Sehen Sie sich die Zeichenfolgenverarbeitung an, Ihr Code würde auf UTF8/UTF16 beschädigt, wenn Sie Abfragen wie die Länge in Bezug auf Grapheme nicht beantworten können. Wenn Sie sich nicht für Unicode interessieren und Dinge in ASCII oder UTF-32 codieren, dann ja, vielleicht ist es für Sie irrelevant.

    Benutzer90843

    24. Mai 2014 um 1:33 Uhr


  • Grapheme oder Zeichen sind nur für die visuelle Anzeige (und manchmal für die Bearbeitung) relevant. Das ist 1 % dessen, was Sie mit Zeichenfolgen tun, und normalerweise isoliert auf GUI-Toolkit-Bibliotheken. Alles andere, was mit Strings gemacht wird, ist völlig agnostisch und kümmert sich nur (auf C, wo die Speicherung explizit ist) um die Speicheranforderungen für den String. In anderen Sprachen, in denen die Speicherung nicht explizit ist, sollten Sie sich nicht einmal darum kümmern.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    24. Mai 2014 um 14:37 Uhr

Benutzeravatar von jsj
jsj

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

Dadurch werden Zeichen in einer UTF-8-Zeichenfolge gezählt … (Gefunden in diesem Artikel: Noch schnellere UTF-8-Zeichenzählung)

Allerdings bin ich immer noch ratlos beim Slicing und Concatenating?!?

  • @R Die Verwendung besteht darin, Pinyin in numerischer Form (ni2hao3ma5) in Pinyin mit Akzenten umzuwandeln. Ich habe jetzt meine eigenen Funktionen geschrieben, basierend auf der inhärenten Bedeutung im ersten Byte eines Unicode-Zeichenpunkts. Es ist ein bisschen klobig, aber es erledigt die Arbeit, ohne dass eine umfangreiche Bibliothek eingebunden werden muss.

    – jsj

    4. September 2011 um 15:56 Uhr

  • @ trideceth12: Ich habe das gleiche selbst gemacht. Es waren nur ein paar Zeilen Perl. Wirklich.

    – tchrist

    6. September 2011 um 18:26 Uhr

  • Ich würde argumentieren, dass Sie fast nie wissen wollen, wie viel “Speicher” vorhanden ist, und was Sie wirklich wollen, wenn Sie über Länge sprechen, sind “Zeichen”, nicht Bytes. Sehen Sie sich die Zeichenfolgenverarbeitung an, Ihr Code würde auf UTF8/UTF16 beschädigt, wenn Sie Abfragen wie die Länge in Bezug auf Grapheme nicht beantworten können. Wenn Sie sich nicht für Unicode interessieren und Dinge in ASCII oder UTF-32 codieren, dann ja, vielleicht ist es für Sie irrelevant.

    Benutzer90843

    24. Mai 2014 um 1:33 Uhr


  • Grapheme oder Zeichen sind nur für die visuelle Anzeige (und manchmal für die Bearbeitung) relevant. Das ist 1 % dessen, was Sie mit Zeichenfolgen tun, und normalerweise isoliert auf GUI-Toolkit-Bibliotheken. Alles andere, was mit Strings gemacht wird, ist völlig agnostisch und kümmert sich nur (auf C, wo die Speicherung explizit ist) um die Speicheranforderungen für den String. In anderen Sprachen, in denen die Speicherung nicht explizit ist, sollten Sie sich nicht einmal darum kümmern.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    24. Mai 2014 um 14:37 Uhr

Benutzeravatar von abahgat
abahgat

Im Allgemeinen sollten wir für Unicode-Zeichen einen anderen Datentyp verwenden.

Sie können beispielsweise den Datentyp wide char verwenden

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

Beachten Sie den Modifikator L, der angibt, dass die Zeichenfolge aus breiten Zeichen besteht.

Die Länge dieser Zeichenfolge kann mit berechnet werden wcslen Funktion, die sich wie verhält strlen.

  • Abgesehen davon, dass breite Zeichen jeweils 4 Bytes umfassen. Also ist “Hallo Welt” 44 Bytes statt 11 Bytes und “大家,你们好” 24 Bytes statt 18 Bytes.

    – jsj

    4. September 2011 um 8:40 Uhr

  • Nun, das bleibt im Allgemeinen der Implementierung überlassen (in einigen Fällen können sie 2 Byte lang sein), aber ich kann Ihren Punkt hier verstehen.

    – abahgat

    4. September 2011 um 8:45 Uhr

  • Zusammenfassung: wchar_t ist NICHT Unicode, da sizeof(wchar_t) vom Compiler abhängig ist

    – Benutzer411313

    4. September 2011 um 11:03 Uhr


  • @ user411312, es kann zum Speichern von Unicode-Zeichen verwendet werden, aber die Codierung ist ein Implementierungsdetail. Beachten Sie, dass der Unicode-Zeichensatz nicht auf eine Codierung festgelegt ist

    – Sebastian

    4. September 2011 um 11:28 Uhr

  • @user411312 wchar_t ist UTF-32 für GCC (zumindest auf Unixoid-Systemen) und UTF-16 auf Windows/msvc – also für die beliebtesten Systeme wchar_t ist (etwas) Unicode

    – mbx

    4. September 2011 um 11:34 Uhr


1412540cookie-checkSo zählen Sie Zeichen in einer Unicode-Zeichenfolge in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy