56 votes

Comment compter les caractères dans une chaîne unicode en C

Disons que j'ai une chaîne de caractères :

char theString[] = "a";

Étant donné que mon codage est utf-8, cette chaîne compte 12 octets (les trois caractères hanzi comptent chacun trois octets, le caractère latin avec le macron compte deux octets et le "a" compte un octet) :

strlen(theString) == 12

Comment puis-je compter le nombre de caractères ? Comment puis-je faire l'équivalent de l'écriture en indice pour que :

theString[3] == ""

Comment puis-je trancher, et attraper de telles chaînes ?

29voto

paxdiablo Points 341644

Vous ne comptez que les caractères dont les deux bits supérieurs ne sont pas définis sur 10 (c'est-à-dire tout ce qui est inférieur à 0x80 ou supérieure à 0xbf ).

C'est parce que tous les caractères dont les deux premiers bits sont définis à 10 sont des octets de continuation UTF-8.

Ver aquí pour une description de l'encodage et de la manière dont strlen peut fonctionner sur une chaîne UTF-8.

Pour découper des chaînes de caractères UTF-8, il faut suivre les mêmes règles. Tout octet commençant par un 0 ou un 11 est le début d'un point de code UTF-8, tous les autres sont des caractères de continuation.

Votre meilleure chance, si vous ne voulez pas utiliser une bibliothèque tierce, est de fournir simplement des fonctions de ce type :

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;

à obtenir, respectivement :

  • la gauche sz Octets UTF-8 d'une chaîne de caractères.
  • le site sz Octets UTF-8 d'une chaîne de caractères, commençant à pos .
  • le reste des octets UTF-8 d'une chaîne de caractères, à partir de l'adresse suivante pos .

Il s'agira d'un élément de base décent pour pouvoir manipuler les cordes suffisamment pour vos besoins.

17voto

Mark Points 21191

Le moyen le plus simple est d'utiliser une bibliothèque telle que UNITÉ DE SOINS INTENSIFS

15voto

Matt Joiner Points 29194

Essayez ceci pour la taille :

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

Exemple d'exécution :

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

Notez que votre exemple comporte une erreur d'une unité. theString[2] == "好"

9voto

Kerrek SB Points 194696

En fonction de votre notion de "caractère", cette question peut être plus ou moins complexe.

Tout d'abord, vous devez transformer votre chaîne d'octets en une chaîne de points de code unicode. Vous pouvez le faire avec iconv() des soins intensifs, bien que si c'est la seule chose que vous faites, iconv() est beaucoup plus facile, et fait partie de POSIX.

Votre chaîne de points de code unicode pourrait être quelque chose comme une chaîne à terminaison nulle. uint32_t[] ou, si vous avez C1x, un tableau de char32_t . La taille de ce tableau (c'est-à-dire son nombre d'éléments, et non sa taille en octets) est le nombre de points de code (plus le terminateur), ce qui devrait vous donner un très bon départ.

Cependant, la notion de "caractère imprimable" est assez complexe, et vous pouvez préférer compter les caractères suivants graphèmes plutôt que des points de code - par exemple, une a avec un accent ^ peut être exprimée sous la forme de deux points de code unicode ou d'un point de code hérité combiné â - les deux sont valides, et la norme unicode exige que les deux soient traités de la même manière. Il existe un processus appelé "normalisation" qui transforme votre chaîne de caractères en une version définie, mais il existe de nombreux graphèmes qui ne peuvent pas être exprimés par un seul point de code et, en général, il est impossible de contourner une bibliothèque appropriée qui comprend cela et compte les graphèmes pour vous.

Cela dit, c'est à vous de décider de la complexité de vos scripts et de la rigueur avec laquelle vous voulez les traiter. La transformation en codepoints unicode est un must, tout ce qui est au-delà est à votre discrétion.

N'hésitez pas à poser des questions sur l'unité de soins intensifs si vous décidez d'en avoir besoin, mais n'hésitez pas à explorer le site beaucoup plus simple iconv() d'abord.

1voto

abahgat Points 6479

En général, nous devrions utiliser un type de données différent pour les caractères unicode.

Par exemple, vous pouvez utiliser le type de données char large

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

Notez le modificateur L qui indique que la chaîne est composée de caractères larges.

La longueur de cette chaîne peut être calculée à l'aide de la fonction wcslen qui se comporte comme strlen .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X