Comment strtok () divise-t-il la chaîne en jetons en C?

114

Veuillez m'expliquer le fonctionnement de la strtok()fonction. Le manuel dit qu'il divise la chaîne en jetons. Je suis incapable de comprendre à partir du manuel ce qu'il fait réellement.

J'ai ajouté des montres stret *pchpour vérifier son fonctionnement lorsque la première boucle while s'est produite, le contenu de strn'était que "ceci". Comment la sortie ci-dessous s'est-elle imprimée à l'écran?

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="- This, a sample string.";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ,.-");
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ,.-");
  }
  return 0;
}

Production:

Splitting string "- Ceci, un exemple de chaîne." en jetons:
Ce
une
échantillon
chaîne
fuddin
la source
5
strtok()modifie sa chaîne d'arguments en terminant les jetons avec NUL avant de retourner. Si vous essayez d'examiner tout le tampon (str []), vous verrez qu'il est modifié entre les appels successifs à strtok().
Michael Foukarakis
Au lieu de regarder str, regarder str[0], str[1], str[2], ...
PMG
@pmg: J'ai regardé str [0] et str [1] .str [1] devrait être '\ 0', mais il y avait un espace.
fuddin
3
Honnêtement, je n'ai jamais pris la peine de vérifier, mais j'imagine qu'il stocke le dernier pointeur passé, ainsi que la position à laquelle il s'est arrêté. Ensuite, il peut simplement continuer si le pointeur est NULL, ou effacer la position et recommencer sinon.
chris
7
@Firegun: variable statique .
DCoder

Réponses:

38

strtok()divise la chaîne en jetons. c'est-à-dire à partir de l'un quelconque des délimiteurs au suivant serait votre seul jeton. Dans votre cas, le jeton de départ sera de "-" et se terminera par l'espace suivant "". Ensuite, le jeton suivant commencera par "" et se terminera par ",". Ici, vous obtenez "This" en sortie. De même, le reste de la chaîne est divisé en jetons d'espace en espace et termine finalement le dernier jeton sur "."

Sachin Shanbhag
la source
la condition de fin pour un jeton devient le jeton de départ du jeton suivant? y a-t-il également un caractère nul placé à la place de la condition de fin?
fuddin
1
@ fahad- Oui, tous les délimiteurs que vous avez seront remplacés par le caractère NUL comme d'autres personnes l'ont également suggéré.
Sachin Shanbhag
Si tous les délimiteurs sont remplacés par Nul, alors pourquoi la chaîne contient-elle "-this"? Il devrait contenir "\ 0"
fuddin
2
@fahad - Il remplace uniquement les caractères de délimitation par NUL, pas tous les caractères entre les délimiteurs. C'est une sorte de division de la chaîne en plusieurs jetons. Vous obtenez "This" parce que c'est entre deux délimiteurs spécifiés et non le "-this".
Sachin Shanbhag
1
@Fahad - Oui, absolument. Tous les espaces, "," et "-" sont remplacés par NUL parce que vous les avez spécifiés comme délimiteurs, pour autant que je sache.
Sachin Shanbhag
212

la fonction d'exécution strtok fonctionne comme ceci

la première fois que vous appelez strtok, vous fournissez une chaîne que vous souhaitez tokenize

char s[] = "this is a string";

dans l'espace de chaîne ci-dessus semble être un bon délimiteur entre les mots, alors utilisons cela:

char* p = strtok(s, " ");

ce qui se passe maintenant est que 's' est recherché jusqu'à ce que le caractère d'espace soit trouvé, le premier jeton est retourné ('this') et p pointe vers ce jeton (chaîne)

afin d'obtenir le jeton suivant et de continuer avec la même chaîne, NULL est passé comme premier argument puisque strtok maintient un pointeur statique vers votre chaîne passée précédemment:

p = strtok(NULL," ");

p pointe maintenant vers 'est'

et ainsi de suite jusqu'à ce qu'il n'y ait plus d'espaces, alors la dernière chaîne est renvoyée comme dernier jeton «chaîne».

plus commodément, vous pouvez l'écrire comme ceci à la place pour imprimer tous les jetons:

for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
  puts(p);
}

ÉDITER:

Si vous voulez stocker les valeurs renvoyées, strtokvous devez copier le jeton dans un autre tampon, par exemple strdup(p);puisque la chaîne d'origine (pointée par le pointeur statique à l'intérieur strtok) est modifiée entre les itérations afin de renvoyer le jeton.

AndersK
la source
Donc, il ne place pas réellement un caractère nul entre la chaîne? Pourquoi ma montre montre-t-elle que la chaîne est laissée uniquement avec "CECI"?
fuddin
4
il remplace en effet le «» qu'il a trouvé par «\ 0». Et, il ne restaure pas '' plus tard, donc votre chaîne est définitivement ruinée.
33
+1 pour le tampon statique, c'est ce que je n'ai pas compris
IEatBagels
1
Un détail très important, absent de la ligne «le premier jeton est retourné et ppointe vers ce jeton» , est qu'il strtokfaut muter la chaîne d'origine en plaçant un caractère nul à la place d'un délimiteur (sinon les autres fonctions de chaîne ne sauraient pas où le jeton se termine). Et il garde également une trace de l'état à l'aide d'une variable statique.
Groo le
@Groo Je pense que j'ai déjà ajouté cela dans l'édition que j'ai fait en 2017, mais vous avez raison.
AndersK
25

strtokconserve une référence interne statique pointant vers le prochain jeton disponible dans la chaîne; si vous lui passez un pointeur NULL, il fonctionnera à partir de cette référence interne.

C'est la raison strtokpour laquelle il n'est pas ré-entrant; dès que vous lui passez un nouveau pointeur, cette ancienne référence interne est écrasée.

John Bode
la source
Qu'entendez-vous par l'ancienne référence interne «se faire écraser». Voulez-vous dire «écrasé»?
ylun.ca
1
@ ylun.ca: oui, c'est ce que je veux dire.
John Bode
10

strtokne change pas le paramètre lui-même ( str). Il stocke ce pointeur (dans une variable statique locale). Il peut ensuite modifier ce vers quoi ce paramètre pointe dans les appels suivants sans que le paramètre ne soit renvoyé. (Et il peut avancer ce pointeur qu'il a conservé comme il en a besoin pour effectuer ses opérations.)

Depuis la strtokpage POSIX :

Cette fonction utilise le stockage statique pour garder une trace de la position actuelle de la chaîne entre les appels.

Il existe une variante thread-safe ( strtok_r) qui ne fait pas ce type de magie.

Tapis
la source
2
Eh bien, les fonctions de la bibliothèque C remontent à l'époque où le threading n'était pas du tout dans l'image (cela n'a commencé à exister qu'en 2011 en ce qui concerne le standard C), donc la ré-entrée n'était pas vraiment importante ( J'imagine). Ce local statique rend la fonction "facile à utiliser" (pour une définition de "facile"). Comme ctimerenvoyer une chaîne statique - pratique (personne n'a besoin de se demander qui devrait la libérer), mais pas rentrante et vous trébuche si vous n'êtes pas très conscient de cela.
Mat
C'est faux: " strtokne change pas le paramètre lui-même ( str)." puts(str);imprime "- Ceci" strtokmodifié depuis str.
MarredCheese
1
@MarredCheese: relisez. Il ne modifie pas le pointeur. Il modifie les données vers lesquelles pointe le pointeur (c'est-à-dire les données de chaîne)
Mat
Oh ok, je n'avais pas réalisé que c'était ce à quoi tu voulais en venir. D'accord.
MarredCheese
8

La première fois que vous l'appelez, vous fournissez la chaîne de tokenize strtok. Et puis, pour obtenir les jetons suivants, il vous suffit de donner NULLà cette fonction, tant qu'elle renvoie un non NULLpointeur.

La strtokfonction enregistre la chaîne que vous avez fournie pour la première fois lorsque vous l'appelez. (Ce qui est vraiment dangereux pour les applications multi-thread)

tibur
la source
8

strtok va tokeniser une chaîne c'est à dire la convertir en une série de sous-chaînes.

Il le fait en recherchant des délimiteurs qui séparent ces jetons (ou sous-chaînes). Et vous spécifiez les délimiteurs. Dans votre cas, vous voulez "" ou "," ou "." ou «-» pour être le délimiteur.

Le modèle de programmation pour extraire ces jetons est que vous remettez strtok votre chaîne principale et l'ensemble des délimiteurs. Ensuite, vous l'appelez à plusieurs reprises, et chaque fois que strtok retournera le prochain jeton qu'il trouve. Jusqu'à ce qu'il atteigne la fin de la chaîne principale, lorsqu'il renvoie un null. Une autre règle est que vous ne transmettez la chaîne que la première fois et NULL pour les fois suivantes. C'est un moyen d'indiquer à strtok si vous démarrez une nouvelle session de tokenisation avec une nouvelle chaîne ou si vous récupérez des jetons d'une session de tokenisation précédente. Notez que strtok se souvient de son état pour la session de création de jetons. Et pour cette raison, il n'est pas réentrant ou thread-safe (vous devriez utiliser strtok_r à la place). Une autre chose à savoir est qu'il modifie en fait la chaîne d'origine. Il écrit '\ 0' pour les délimiteurs qu'il trouve.

Une façon d'appeler strtok, succinctement, est la suivante:

char str[] = "this, is the string - I want to parse";
char delim[] = " ,-";
char* token;

for (token = strtok(str, delim); token; token = strtok(NULL, delim))
{
    printf("token=%s\n", token);
}

Résultat:

this
is
the
string
I
want
to
parse
Ziffusion
la source
5

strtok modifie sa chaîne d'entrée. Il y place des caractères nuls ('\ 0') afin de renvoyer des bits de la chaîne d'origine sous forme de jetons. En fait, strtok n'alloue pas de mémoire. Vous comprendrez peut-être mieux si vous dessinez la chaîne sous la forme d'une séquence de cases.

xpmatteo
la source
3

Pour comprendre comment strtok()fonctionne, il faut d'abord savoir ce qu'est une variable statique . Ce lien l' explique assez bien ...

La clé du fonctionnement de strtok()est de conserver l'emplacement du dernier séparateur entre les appels secondaires (c'est pourquoi strtok()continue d'analyser la chaîne très originale qui lui est transmise lorsqu'elle est invoquée avec un null pointerappel successif).

Jetez un œil à ma propre strtok()implémentation, appelée zStrtok(), qui a une fonctionnalité légèrement différente de celle fournie parstrtok()

char *zStrtok(char *str, const char *delim) {
    static char *static_str=0;      /* var to store last address */
    int index=0, strlength=0;           /* integers for indexes */
    int found = 0;                  /* check if delim is found */

    /* delimiter cannot be NULL
    * if no more char left, return NULL as well
    */
    if (delim==0 || (str == 0 && static_str == 0))
        return 0;

    if (str == 0)
        str = static_str;

    /* get length of string */
    while(str[strlength])
        strlength++;

    /* find the first occurance of delim */
    for (index=0;index<strlength;index++)
        if (str[index]==delim[0]) {
            found=1;
            break;
        }

    /* if delim is not contained in str, return str */
    if (!found) {
        static_str = 0;
        return str;
    }

    /* check for consecutive delimiters
    *if first char is delim, return delim
    */
    if (str[0]==delim[0]) {
        static_str = (str + 1);
        return (char *)delim;
    }

    /* terminate the string
    * this assignmetn requires char[], so str has to
    * be char[] rather than *char
    */
    str[index] = '\0';

    /* save the rest of the string */
    if ((str + index + 1)!=0)
        static_str = (str + index + 1);
    else
        static_str = 0;

        return str;
}

Et voici un exemple d'utilisation

  Example Usage
      char str[] = "A,B,,,C";
      printf("1 %s\n",zStrtok(s,","));
      printf("2 %s\n",zStrtok(NULL,","));
      printf("3 %s\n",zStrtok(NULL,","));
      printf("4 %s\n",zStrtok(NULL,","));
      printf("5 %s\n",zStrtok(NULL,","));
      printf("6 %s\n",zStrtok(NULL,","));

  Example Output
      1 A
      2 B
      3 ,
      4 ,
      5 C
      6 (null)

Le code provient d' une bibliothèque de traitement de chaînes que je gère sur Github , appelée zString. Jetez un œil au code, ou même contribuez :) https://github.com/fnoyanisi/zString

fnisi
la source
3

C'est ainsi que j'ai implémenté strtok, pas si génial mais après avoir travaillé 2 heures dessus, il a finalement fonctionné. Il prend en charge plusieurs délimiteurs.

#include "stdafx.h"
#include <iostream>
using namespace std;

char* mystrtok(char str[],char filter[]) 
{
    if(filter == NULL) {
        return str;
    }
    static char *ptr = str;
    static int flag = 0;
    if(flag == 1) {
        return NULL;
    }
    char* ptrReturn = ptr;
    for(int j = 0; ptr != '\0'; j++) {
        for(int i=0 ; filter[i] != '\0' ; i++) {
            if(ptr[j] == '\0') {
                flag = 1;
                return ptrReturn;
            }
            if( ptr[j] == filter[i]) {
                ptr[j] = '\0';
                ptr+=j+1;
                return ptrReturn;
            }
        }
    }
    return NULL;
}

int _tmain(int argc, _TCHAR* argv[])
{
    char str[200] = "This,is my,string.test";
    char *ppt = mystrtok(str,", .");
    while(ppt != NULL ) {
        cout<< ppt << endl;
        ppt = mystrtok(NULL,", ."); 
    }
    return 0;
}
Dipak
la source
1

Voici mon implémentation qui utilise une table de hachage pour le délimiteur, ce qui signifie qu'il O (n) au lieu de O (n ^ 2) (voici un lien vers le code) :

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

#define DICT_LEN 256

int *create_delim_dict(char *delim)
{
    int *d = (int*)malloc(sizeof(int)*DICT_LEN);
    memset((void*)d, 0, sizeof(int)*DICT_LEN);

    int i;
    for(i=0; i< strlen(delim); i++) {
        d[delim[i]] = 1;
    }
    return d;
}



char *my_strtok(char *str, char *delim)
{

    static char *last, *to_free;
    int *deli_dict = create_delim_dict(delim);

    if(!deli_dict) {
        /*this check if we allocate and fail the second time with entering this function */
        if(to_free) {
            free(to_free);
        }
        return NULL;
    }

    if(str) {
        last = (char*)malloc(strlen(str)+1);
        if(!last) {
            free(deli_dict);
            return NULL;
        }
        to_free = last;
        strcpy(last, str);
    }

    while(deli_dict[*last] && *last != '\0') {
        last++;
    }
    str = last;
    if(*last == '\0') {
        free(deli_dict);
        free(to_free);
        deli_dict = NULL;
        to_free = NULL;
        return NULL;
    }
    while (*last != '\0' && !deli_dict[*last]) {
        last++;
    }

    *last = '\0';
    last++;

    free(deli_dict);
    return str;
}

int main()
{
    char * str = "- This, a sample string.";
    char *del = " ,.-";
    char *s = my_strtok(str, del);
    while(s) {
        printf("%s\n", s);
        s = my_strtok(NULL, del);
    }
    return 0;
}
Kohn1001
la source
1

strtok () stocke le pointeur dans une variable statique à l'endroit où vous l'avez laissé la dernière fois, donc lors de son deuxième appel, lorsque nous passons la valeur null, strtok () obtient le pointeur de la variable statique.

Si vous fournissez le même nom de chaîne, il recommence à partir du début.

De plus strtok () est destructif, c'est-à-dire qu'il modifie la chaîne de caractères d'origine. alors assurez-vous d'avoir toujours une copie de l'original.

Un autre problème de l'utilisation de strtok () est que comme il stocke l'adresse dans des variables statiques, dans la programmation multithread, appeler strtok () plus d'une fois provoquera une erreur. Pour cela, utilisez strtok_r ().

Vaibhav
la source
0

Pour ceux qui ont encore du mal à comprendre cette strtok()fonction, jetez un œil à cet exemple de pythontutor , c'est un excellent outil pour visualiser votre code C (ou C ++, Python ...).

Au cas où le lien serait rompu, collez:

#include <stdio.h>
#include <string.h>

int main()
{
    char s[] = "Hello, my name is? Matthew! Hey.";
    char* p;
    for (char *p = strtok(s," ,?!."); p != NULL; p = strtok(NULL, " ,?!.")) {
      puts(p);
    }
    return 0;
}

Les crédits vont à Anders K.


la source
0

vous pouvez scanner le tableau de caractères à la recherche du jeton si vous l'avez trouvé, imprimez simplement une nouvelle ligne sinon imprimez le caractère.

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

int main()
{
    char *s;
    s = malloc(1024 * sizeof(char));
    scanf("%[^\n]", s);
    s = realloc(s, strlen(s) + 1);
    int len = strlen(s);
    char delim =' ';
    for(int i = 0; i < len; i++) {
        if(s[i] == delim) {
            printf("\n");
        }
        else {
            printf("%c", s[i]);
        }
    }
    free(s);
    return 0;
}
Fahad Alotaibi
la source
0

Donc, ceci est un extrait de code pour aider à mieux comprendre ce sujet.

Jetons d'impression

Tâche: À partir d'une phrase, imprimez chaque mot de la phrase sur une nouvelle ligne.

char *s;
s = malloc(1024 * sizeof(char));
scanf("%[^\n]", s);
s = realloc(s, strlen(s) + 1);
//logic to print the tokens of the sentence.
for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
    printf("%s\n",p);
}

Contribution: How is that

Résultat:

How
is
that

Explication: Ici, la fonction "strtok ()" est utilisée et elle est itérée en utilisant la boucle for pour imprimer les jetons sur des lignes séparées.

La fonction prendra des paramètres comme «chaîne» et «point de rupture» et coupera la chaîne à ces points de rupture et formera des jetons. Désormais, ces jetons sont stockés dans «p» et sont ensuite utilisés pour l'impression.

tr_abhishek
la source
Je pense qu'expliquer via un exemple est bien mieux que de se référer à un doc.
tr_abhishek