2010-10-08 83 views
73

Per favore spiegami il funzionamento della funzione strtok(). Il manuale dice che interrompe la stringa in token. Non sono in grado di capire dal manuale cosa effettivamente fa.In che modo strtok() divide la stringa in token in C?

Ho aggiunto orologi su str e *pch per verificarne il funzionamento, quando si è verificato il primo ciclo, il contenuto di str era solo "questo". In che modo l'output mostrato sotto è stato stampato sullo schermo?

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

uscita:

 
Splitting string "- This, a sample string." into tokens: 
This 
a 
sample 
string 
+3

'strtok()' modifica la sua stringa argomento terminando i token con NUL prima di tornare. Se provate ad esaminare l'intero buffer (str []) vedrete che viene modificato tra chiamate successive a 'strtok()'. –

+0

Invece di guardare 'str', guarda' str [0] ',' str [1] ',' str [2] ', ... – pmg

+0

@pmg: Ho guardato str [0] e str [1] .str [1] dovrebbe essere '\ 0', ma era uno spazio lì. –

risposta

34

strtok() suddivide la stringa in token. cioè, partire da uno qualsiasi dei delimitatori al prossimo sarebbe il tuo token. Nel tuo caso, il token di partenza sarà da "-" e terminerà con lo spazio successivo "". Quindi il token successivo inizierà da "" e terminerà con ",". Qui ottieni "This" come output. Allo stesso modo il resto della stringa viene diviso in token da spazio a spazio e infine finisce l'ultimo token su "."

+0

la condizione finale per un token diventa il token di partenza del token successivo? Esiste anche un carattere nul posto al posto della condizione finale? –

+0

@ fahad- Sì, tutti i delimitatori che hai saranno sostituiti dal carattere NUL come suggerito anche da altre persone. –

+0

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

6

La prima volta che si chiama, è necessario fornire la stringa da tokenize a strtok. E poi, per ottenere i seguenti token, basta dare NULL a quella funzione, purché restituisca un puntatore non NULL.

La funzione strtok registra la stringa fornita per la prima volta quando viene chiamata. (Che è veramente pericoloso per le applicazioni multi-thread)

4

strtok modifica la stringa di input. Inserisce caratteri nulli ('\ 0') al suo interno in modo che restituisca bit della stringa originale come token. Infatti strtok non assegna memoria. Puoi capirlo meglio se disegni la sequenza come una sequenza di scatole.

141

la funzione strtok runtime funziona così

la prima volta che si chiama strtok si fornisce una stringa che si desidera tokenize

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

nello spazio di stringa sopra sembra essere un buon delimitatore tra le parole così lascia l'uso che:

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

cosa succede ora è che 's' è cercato fino a quando viene trovato il carattere di spazio, il primo token viene restituito ('questo') un dp punta a quel segno (stringa)

al fine di ottenere token successivo e di continuare con lo stesso NULL stringa viene passato come primo argomento dal strtok mantiene un puntatore statica al precedente stringa passata:

p = strtok(NULL," "); 

p ora punta a 'è'

e così via fino a quando non più spazi possono essere trovati, quindi l'ultima stringa viene restituita come l'ultimo token 'stringa'.più

comodamente si potrebbe scrivere in questo modo, invece di stampare tutti i token:

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

EDIT:

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

+0

Quindi in realtà non inserisce un carattere nul tra le stringhe? Perché il mio orologio mostra che la stringa viene lasciata solo con "QUESTA"? –

+3

sostituisce effettivamente "" trovato con "\ 0". E, non viene ripristinato più tardi, quindi la tua stringa è rovinata per sempre. – Arkadiy

+0

Una bella spiegazione. – Nagaraju

5

strtok consente di tokenizzare una stringa, ovvero convertirla in una serie di sottostringhe.

Ciò avviene ricercando delimitatori che separano questi token (o sottostringhe). E tu specifichi i delimitatori. Nel tuo caso, vuoi "" o "," o "." o '-' essere il delimitatore.

Il modello di programmazione per estrarre questi token è che passi la stringa principale e il set di delimitatori. Quindi lo chiami ripetutamente, e ogni volta che strtok restituirà il token successivo che trova. Fino a quando 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 strtok se stai iniziando una nuova sessione di tokenizing con una nuova stringa, o stai recuperando token da una precedente sessione di tokenizzazione. Si noti che strtok memorizza il suo stato per la sessione di tokenizzazione. E per questo motivo non è rientranti né 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 richiamare strtok, succintamente, è la 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 
9

strtok non cambia il parametro stesso (str). Memorizza quel puntatore (in una variabile statica locale). Può quindi modificare ciò che il parametro punta a nelle chiamate successive senza che il parametro sia passato. (E può avanzare quello puntatore ha mantenuto comunque deve eseguire le sue operazioni.)

Dalla pagina POSIX strtok:

Questa funzione utilizza l'archiviazione statica per tenere traccia della posizione corrente di stringa tra chiamate .

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

+0

Qual è stato il ragionamento dietro a questo mi chiedo. – chris

+2

Bene, le funzioni della libreria C risalgono al lontano passato, quando il threading non era affatto nell'immagine (che era iniziato solo nel 2011 per quanto riguarda lo standard C), quindi la re-entrancy non era davvero importante (Suppongo). Quel locale statico rende la funzione "facile da usare" (per alcune definizioni di "facile"). Come 'ctime' restituendo una stringa statica - pratica (nessuno deve chiedersi chi dovrebbe liberarla), ma non ri-entrante e ti fa inciampare se non ne sei a conoscenza. – Mat

18

strtok mantiene un riferimento interno statico che punta al successivo token disponibile nella stringa; se si passa un puntatore NULL, funzionerà da quel riferimento interno.

Questa è la ragione per cui strtok non è rientranti; non appena si passa un nuovo puntatore, quel vecchio riferimento interno viene danneggiato.

+0

Che cosa intendi con il vecchio riferimento interno "farsi sbigottire". Intendi "sovrascritto"? –

+0

@ ylun.ca: sì, questo è quello che intendo. –

2

Per comprendere come funziona il protocollo strtok(), è necessario innanzitutto sapere cos'è un static variable. This link lo spiega abbastanza bene ....

La chiave per il funzionamento del strtok() è preservare la posizione dell'ultimo separatore tra le chiamate seccessive (ecco perché strtok() continua ad analizzare la stringa molto originale che viene passata ad esso quando viene richiamata con un null pointer nelle chiamate successive). .

dai un'occhiata alla mia strtok() implementazione, chiamato zStrtok(), che ha una funzionalità sligtly diverso da quello fornito da strtok()

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

E qui è 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 è da a string processing library I maintain on Github, chiamato zString. Dai un'occhiata al codice, o persino contribuire :) https://github.com/fnoyanisi/zString

1

Qui è la mia applicazione che utilizza la tabella hash per il delimitatore, il che significa che O (n) invece di O (n^2) (here is a link to the 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; 
} 
0

Questo è il modo in cui ho implementato strtok, non così bello, ma dopo aver lavorato 2 ore su di esso finalmente ha funzionato. Supporta i delimitatori multipli.

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

strtok() memorizza il puntatore nella variabile statica in cui è stata l'ultima volta era interrotto, così la sua seconda chiamata, quando si passa il null, strtok() ottiene il puntatore dalla variabile statica.

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

Inoltre lo strtok() è distruttivo, cioè esegue delle modifiche alla stringa originale. quindi assicurati di avere sempre una copia di quella originale.

Un altro problema dell'utilizzo di strtok() è che mentre memorizza l'indirizzo in variabili statiche, in programmazione multithreading la chiamata a strtok() più di una volta causerà un errore. Per questo usa strtok_r().