182 votes

Diviser la chaîne avec des délimiteurs en C

Comment puis-je écrire une fonction pour diviser et retourner un tableau pour une chaîne de caractères avec des délimiteurs dans le langage de programmation C?

char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
str_split(str,',');

32 votes

Vous pouvez utiliser la fonction strtok de la bibliothèque standard pour accomplir la même chose.

0 votes

0 votes

Un commentaire... le point clé d'une fonction de la famille strtok() est de comprendre les variables statiques en C, c'est-à-dire comment elles se comportent entre deux appels de fonction successifs dans lesquels elles sont utilisées. Voici mon code ci-dessous.

186voto

hmjd Points 76411

Vous pouvez utiliser la fonction strtok() pour diviser une chaîne de caractères (et spécifier le délimiteur à utiliser). Notez que strtok() modifiera la chaîne qui lui est passée. Si la chaîne d'origine est requise ailleurs, faites-en une copie et passez la copie à strtok().

ÉDIT:

Exemple (remarquez qu'il ne gère pas les délimiteurs consécutifs, par exemple "JAN,,,FEB,MAR"):

#include 
#include 
#include 
#include 

char** str_split(char* a_str, const char a_delim)
{
    char** result    = 0;
    size_t count     = 0;
    char* tmp        = a_str;
    char* last_comma = 0;
    char delim[2];
    delim[0] = a_delim;
    delim[1] = 0;

    /* Comptez le nombre d'éléments pouvant être extraits. */
    while (*tmp)
    {
        if (a_delim == *tmp)
        {
            count++;
            last_comma = tmp;
        }
        tmp++;
    }

    /* Ajoutez de l'espace pour le jeton final. */
    count += last_comma < (a_str + strlen(a_str) - 1);

    /* Ajoutez de l'espace pour la chaîne nulle terminale afin que l'appelant sache où se termine la liste des chaînes retournées. */
    count++;

    result = malloc(sizeof(char*) * count);

    if (result)
    {
        size_t idx  = 0;
        char* token = strtok(a_str, delim);

        while (token)
        {
            assert(idx < count);
            *(result + idx++) = strdup(token);
            token = strtok(0, delim);
        }
        assert(idx == count - 1);
        *(result + idx) = 0;
    }

    return result;
}

int main()
{
    char months[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    char** tokens;

    printf("months=[%s]\n\n", months);

    tokens = str_split(months, ',');

    if (tokens)
    {
        int i;
        for (i = 0; *(tokens + i); i++)
        {
            printf("month=[%s]\n", *(tokens + i));
            free(*(tokens + i));
        }
        printf("\n");
        free(tokens);
    }

    return 0;
}

Sortie:

$ ./main.exe
months=[JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC]

month=[JAN]
month=[FEB]
month=[MAR]
month=[APR]
month=[MAY]
month=[JUN]
month=[JUL]
month=[AUG]
month=[SEP]
month=[OCT]
month=[NOV]
month=[DEC]

2 votes

Salut. Je pense que la fonction a codé en dur "," comme séparateur : char* token = strtok(a_str, ",");

1 votes

@SteveP, bien vu. Ce code est là depuis des mois et personne ne l'a remarqué. Je vais le corriger sous peu.

4 votes

Comme il s'agit peut-être de la question/réponse canonique sur Stack Overflow à ce sujet, n'y a-t-il pas quelques limitations en ce qui concerne le multi-threading en utilisant strtok?

85voto

Tyler Points 16516

Je pense que strsep est toujours le meilleur outil pour cela :

while ((token = strsep(&str, ","))) my_fn(token);

C'est littéralement une ligne qui divise une chaîne de caractères.

Les parenthèses supplémentaires sont un élément stylistique pour indiquer que nous testons intentionnellement le résultat d'une affectation, et non un opérateur d'égalité ==.

Pour que ce modèle fonctionne, token et str ont tous deux le type char *. Si vous avez commencé avec une chaîne de caractères littérale, vous voudriez d'abord en faire une copie :

// Modèle plus général :
const char *my_str_literal = "JAN,FEB,MAR";
char *token, *str, *tofree;

tofree = str = strdup(my_str_literal);  // Nous possédons maintenant la mémoire de str.
while ((token = strsep(&str, ","))) my_fn(token);
free(tofree);

Si deux délimiteurs apparaissent ensemble dans str, vous obtiendrez une valeur de token qui est une chaîne vide. La valeur de str est modifiée à chaque délimiteur rencontré, en écrivant par-dessus un octet nul - une autre bonne raison de copier d'abord la chaîne de caractères analysée.

Dans un commentaire, quelqu'un a suggéré que strtok est mieux que strsep parce que strtok est plus portable. Ubuntu et Mac OS X ont strsep ; il est raisonnable de supposer que d'autres systèmes de type Unix en ont aussi. Windows ne dispose pas de strsep, mais il possède strbrk qui permet ce remplacement court et efficace de strsep :

char *strsep(char **stringp, const char *delim) {
  if (*stringp == NULL) { return NULL; }
  char *token_start = *stringp;
  *stringp = strpbrk(token_start, delim);
  if (*stringp) {
    **stringp = '\0';
    (*stringp)++;
  }
  return token_start;
}

Voici une bonne explication de strsep vs strtok. Les avantages et les inconvénients peuvent être jugés subjectivement ; cependant, je pense que c'est un signe révélateur que strsep a été conçu comme un remplacement de strtok.

5 votes

Plus précisément sur la portabilité : ce n'est pas POSIX 7, mais dérivé de BSD, et mis en œuvre sur glibc.

0 votes

Je m'apprêtais justement à demander... Pelle's C a strdup(), mais pas de strsep().

1 votes

Pourquoi tofree est celui qui est libéré et non str?

32voto

thenetimp Points 2300

Le découpage de chaîne, ce code devrait vous mettre dans la bonne direction.

int main(void) {
  char st[] ="Où il y a de la volonté, il y a un chemin.";
  char *ch;
  ch = strtok(st, " ");
  while (ch != NULL) {
    printf("%s\n", ch);
    ch = strtok(NULL, " ,");
  }
  getch();
  return 0;
}

13voto

user1090944 Points 78

La méthode ci-dessous fera tout le travail (allocation de mémoire, comptage de la longueur) pour vous. Plus d'informations et la description peuvent être trouvées ici - Implémentation de la méthode Java String.split() pour diviser une chaîne C

int split (const char *str, char c, char ***arr)
{
    int count = 1;
    int token_len = 1;
    int i = 0;
    char *p;
    char *t;

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
            count++;
        p++;
    }

    *arr = (char**) malloc(sizeof(char*) * count);
    if (*arr == NULL)
        exit(1);

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
        {
            (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
            if ((*arr)[i] == NULL)
                exit(1);

            token_len = 0;
            i++;
        }
        p++;
        token_len++;
    }
    (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
    if ((*arr)[i] == NULL)
        exit(1);

    i = 0;
    p = str;
    t = ((*arr)[i]);
    while (*p != '\0')
    {
        if (*p != c && *p != '\0')
        {
            *t = *p;
            t++;
        }
        else
        {
            *t = '\0';
            i++;
            t = ((*arr)[i]);
        }
        p++;
    }

    return count;
}

Comment l'utiliser:

int main (int argc, char ** argv)
{
    int i;
    char *s = "Bonjour, ceci est un module de test pour la division de chaîne de caractères.";
    int c = 0;
    char **arr = NULL;

    c = split(s, ' ', &arr);

    printf("trouvé %d jetons.\n", c);

    for (i = 0; i < c; i++)
        printf("chaîne #%d: %s\n", i, arr[i]);

    return 0;
}

4 votes

Huh Three star Programmeur :)) Cela semble intéressant.

0 votes

Lorsque je fais cela, cela ajoute soit trop à la dernière jeton, soit lui alloue trop de mémoire. Voici la sortie : a trouvé 10 jetons. chaîne #0 : Bonjour, chaîne #1 : cette chaîne #2 : est chaîne #3 : un chaîne #4 : test chaîne #5 : module chaîne #6 : pour chaîne #7 : le chaîne #8 : splitting.¢

7 votes

Cet exemple a plusieurs fuites de mémoire. Pour toute personne lisant ceci, ne pas utiliser cette approche. Préférez plutôt les approches de tokenisation strtok ou strsep.

1voto

jsn Points 2660

Non testé, probablement incorrect, mais devrait vous donner un bon point de départ sur la façon dont cela devrait fonctionner:

*char[] str_split(char* str, char delim) {

    int begin = 0;
    int end = 0;
    int j = 0;
    int i = 0;
    char *buf[NUM];

    while (i < strlen(str)) {

        if(*str == delim) {

            buf[j] = malloc(sizeof(char) * (end-begin));
            strncpy(buf[j], *(str + begin), (end-begin));
            begin = end;
            j++;

        }

        end++;
        i++;

    }

    return buf;

}

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