In che modo strtok () divide la stringa in token in C?


114

Per favore spiegami il funzionamento della strtok()funzione. Il manuale dice che spezza la stringa in token. Non riesco a capire dal manuale cosa fa effettivamente.

Ho aggiunto orologi stre *pchper verificarne il funzionamento quando si è verificato il primo ciclo while, i contenuti di strerano solo "questo". Come è stato stampato sullo schermo l'output mostrato di seguito?

/* 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;
}

Produzione:

Stringa di divisione "- Questa, una stringa di esempio." in gettoni:
Questo
un'
campione
corda

5
strtok()modifica la sua stringa di argomenti terminando i token con NUL prima di tornare. Se provi ad esaminare l'intero buffer (str []) vedrai che viene modificato tra le successive chiamate a strtok().
Michael Foukarakis

Invece di guardare str, guardare str[0], str[1], str[2], ...
PMG

@pmg: ho visto str [0] e str [1] .str [1] dovrebbero essere "\ 0", ma lì c'era uno spazio.
fuddin

3
Onestamente non mi sono mai preso la briga di controllare, ma immagino che memorizzi l'ultimo puntatore passato, insieme alla posizione in cui era stato interrotto. Quindi può semplicemente continuare se il puntatore è NULL, o cancellare la posizione e ricominciare da capo in caso contrario.
chris

7
@Firegun: variabile statica .
DCoder

Risposte:


38

strtok()divide la stringa in token. cioè a partire da uno qualsiasi dei delimitatori a quello successivo sarebbe il tuo unico token. Nel tuo caso, il token iniziale sarà da "-" e finirà con lo spazio successivo "". Quindi il token successivo inizierà da "" e finirà con ",". Qui ottieni "Questo" come output. Allo stesso modo il resto della stringa viene diviso in token da spazio a spazio e infine termina l'ultimo token su "."


la condizione finale per un gettone diventa il gettone iniziale del gettone successivo? inoltre c'è un carattere nul posto al posto della condizione finale?
fuddin

1
@ fahad- Sì, tutti i delimitatori che hai saranno sostituiti dal carattere NUL come hanno suggerito anche altre persone.
Sachin Shanbhag

Se tutti i delimitatori sono sostituiti da Nul, perché la stringa contiene "-questo"? Dovrebbe contenere "\ 0"
fuddin

2
@fahad - Sostituisce solo i caratteri delimitatori con NUL, non tutti i caratteri tra i delimitatori. È un po 'come suddividere la stringa in più token. Ottieni "Questo" perché è compreso tra due delimitatori specificati e non "-questo".
Sachin Shanbhag

1
@ Fahad - Sì, assolutamente. Tutti gli spazi, "," e "-" sono sostituiti da NUL perché li hai specificati come delimitatori, per quanto ho capito.
Sachin Shanbhag,

212

la funzione runtime di strtok funziona in questo modo

la prima volta che chiami strtok fornisci una stringa che vuoi tokenizzare

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

nella stringa di cui sopra lo spazio sembra essere un buon delimitatore tra le parole, quindi usiamolo:

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

quello che succede ora è che 's' viene cercato finché non viene trovato il carattere spazio, viene restituito il primo token ('this') ep punta a quel token (stringa)

per ottenere il token successivo e per continuare con la stessa stringa, NULL viene passato come primo argomento poiché strtok mantiene un puntatore statico alla stringa passata precedente:

p = strtok(NULL," ");

p ora punta a "è"

e così via fino a quando non è più possibile trovare spazi, l'ultima stringa viene restituita come ultimo token "stringa".

più convenientemente potresti scriverlo in questo modo invece di stampare tutti i token:

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

MODIFICARE:

Se si desidera memorizzare i valori restituiti da strtokè necessario copiare il token in un altro buffer, ad esempio strdup(p);poiché la stringa originale (indicata dal puntatore statico all'interno strtok) viene modificata tra le iterazioni per restituire il token.


Quindi non inserisce effettivamente un carattere nul tra la stringa? Perché il mio orologio mostra che la stringa è lasciata solo con "QUESTO"?
fuddin

4
in effetti sostituisce "" trovato con "\ 0". E non si ripristina '' in seguito, quindi la tua stringa è rovinata per sempre.

33
+1 per il buffer statico, questo è quello che non ho capito
IEatBagels

1
Un dettaglio molto importante, mancante dalla riga "il primo token viene restituito e ppunta a quel token" , è che strtokdeve mutare la stringa originale inserendo un carattere nullo al posto di un delimitatore (altrimenti altre funzioni di stringa non saprebbero dove il token finisce). Inoltre tiene traccia dello stato utilizzando una variabile statica.
Groo

@Groo penso di averlo già aggiunto nell'Edit che ho fatto nel 2017, ma hai ragione.
AndersK

25

strtokmantiene un riferimento interno statico che punta al successivo token disponibile nella stringa; se gli passi un puntatore NULL, funzionerà da quel riferimento interno.

Questo è il motivo per cui strtoknon rientra; non appena gli si passa un nuovo puntatore, quel vecchio riferimento interno viene cancellato.


Cosa intendi con il vecchio riferimento interno "essere distrutto". Intendi "sovrascritto"?
ylun.ca

1
@ ylun.ca: sì, è quello che intendo.
John Bode

10

strtoknon modifica il parametro stesso ( str). Memorizza quel puntatore (in una variabile statica locale). Può quindi modificare ciò a cui punta quel parametro nelle chiamate successive senza che il parametro venga restituito. (E può far avanzare il puntatore che ha mantenuto comunque deve eseguire le sue operazioni.)

Dalla strtokpagina POSIX :

Questa funzione utilizza la memoria statica per tenere traccia della posizione corrente della stringa tra le chiamate.

C'è una variante thread-safe ( strtok_r) che non fa questo tipo di magia.


2
Bene, le funzioni della libreria C risalgono a molto tempo fa, quando il threading non era affatto nell'immagine (che ha iniziato a esistere solo nel 2011 per quanto riguarda lo standard C), quindi il rientro non era davvero importante ( Suppongo). Quel locale statico rende la funzione "facile da usare" (per qualche definizione di "facile"). Come ctimerestituire una stringa statica - pratica (nessuno deve chiedersi chi dovrebbe liberarla), ma non rientrante e ti fa inciampare se non ne sei molto consapevole.
Mat

Questo è sbagliato: " strtoknon cambia il parametro stesso ( str)." puts(str);stampa "- Questo" da quando è stato strtokmodificato str.
MarredCheese

1
@MarredCheese: leggi di nuovo. Non modifica il puntatore. Modifica i dati a cui punta il puntatore (cioè i dati della stringa)
Mat

Oh ok, non mi rendevo conto che è quello che stai cercando. Concordato.
MarredCheese

8

La prima volta che lo chiami, fornisci la stringa a cui tokenizzare strtok. E poi, per ottenere i seguenti token, basta dare NULLa quella funzione, purché restituisca un non NULLpuntatore.

La strtokfunzione registra la stringa che hai fornito per la prima volta quando la chiami. (Che è davvero pericoloso per le applicazioni multi-thread)


8

strtok tokenizzerà una stringa, ovvero la convertirà in una serie di sottostringhe.

Lo fa cercando i delimitatori che separano questi token (o sottostringhe). E tu specifichi i delimitatori. Nel tuo caso, vuoi "o", "o". " o "-" per essere il delimitatore.

Il modello di programmazione per estrarre questi token è che si strtok manualmente la stringa principale e il set di delimitatori. Quindi lo chiami ripetutamente e ogni volta che strtok restituirà il token successivo che trova. Finché non raggiunge la fine della stringa principale, quando restituisce un valore nullo. Un'altra regola è che si passa la stringa solo la prima volta e NULL per le volte successive. Questo è un modo per dire a strtok se stai iniziando una nuova sessione di tokenizzazione con una nuova stringa, o stai recuperando token da una precedente sessione di tokenizzazione. Nota che strtok ricorda il suo stato per la sessione di tokenizzazione. E per questo motivo non è rientrante o thread-safe (dovresti invece usare strtok_r). Un'altra cosa da sapere è che in realtà modifica la stringa originale. Scrive "\ 0" per i delimitatori che trova.

Un modo per invocare strtok, in modo succinto, è il seguente:

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

Risultato:

this
is
the
string
I
want
to
parse

5

strtok modifica la sua stringa di input. Inserisce caratteri nulli ("\ 0") in modo che restituisca bit della stringa originale come token. Infatti strtok non alloca memoria. Puoi capirlo meglio se disegni la stringa come una sequenza di riquadri.


3

Per capire come strtok()funziona, è necessario prima sapere cos'è una variabile statica . Questo link lo spiega abbastanza bene ....

La chiave per il funzionamento di strtok()è preservare la posizione dell'ultimo separatore tra chiamate seccessive (ecco perché strtok()continua ad analizzare la stringa molto originale che gli viene passata quando viene invocato con a null pointernelle chiamate successive) ..

Dai un'occhiata alla mia strtok()implementazione, chiamata zStrtok(), che ha una funzionalità leggermente diversa da quella fornita dastrtok()

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

Ed ecco un esempio di utilizzo

  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)

Il codice proviene da una libreria di elaborazione delle stringhe che mantengo su Github , chiamata zString. Dai un'occhiata al codice o contribuisci anche tu :) https://github.com/fnoyanisi/zString


3

È così che ho implementato strtok, non eccezionale ma dopo aver lavorato 2 ore su di esso finalmente ha funzionato. Supporta più delimitatori.

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


1

Ecco la mia implementazione che utilizza la tabella hash per il delimitatore, il che significa che O (n) invece di O (n ^ 2) (ecco un collegamento al codice) :

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

1

strtok () memorizza il puntatore nella variabile statica dove l'avevi interrotto l'ultima volta, quindi alla sua seconda chiamata, quando passiamo il null, strtok () ottiene il puntatore dalla variabile statica.

Se fornisci lo stesso nome di stringa, ricomincia dall'inizio.

Inoltre strtok () è distruttivo, cioè apporta modifiche alla stringa originale. quindi assicurati di avere sempre una copia dell'originale.

Un altro problema dell'utilizzo di strtok () è che poiché memorizza l'indirizzo in variabili statiche, nella programmazione multithread chiamare strtok () più di una volta causerà un errore. Per questo usa strtok_r ().


0

Per coloro che hanno ancora difficoltà a comprendere questa strtok()funzione, dai un'occhiata a questo esempio di pythontutor , è un ottimo strumento per visualizzare il tuo codice C (o C ++, Python ...).

Nel caso in cui il collegamento si sia interrotto, incolla:

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

I crediti vanno ad Anders K.


0

puoi scansionare l'array di caratteri cercando il token se lo trovi solo stampa una nuova riga altrimenti stampa il carattere.

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

0

Quindi, questo è uno snippet di codice per comprendere meglio questo argomento.

Token di stampa

Compito: data una frase, stampa ogni parola della frase in una nuova riga.

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

Ingresso: How is that

Risultato:

How
is
that

Spiegazione: Quindi qui, viene utilizzata la funzione "strtok ()" e viene ripetuta utilizzando il ciclo for per stampare i token in righe separate.

La funzione prenderà i parametri come "stringa" e "punto di interruzione" e spezzerà la stringa in corrispondenza di quei punti di interruzione e dei token di forma. Ora, quei token sono memorizzati in "p" e vengono utilizzati ulteriormente per la stampa.


Penso che spiegare tramite un esempio sia molto meglio che fare riferimento a qualche documento.
tr_abhishek
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.