2010-10-08 206 views
73

Explícame el funcionamiento de la función strtok().El manual dice que rompe la cadena en tokens. No puedo entender por el manual lo que realmente hace.¿Cómo divide strtok() la cadena en tokens en C?

I añadió relojes en str y *pch para comprobar su funcionamiento, cuando se produjo el primer bucle mientras que, el contenido de str fueron sólo "este". ¿Cómo se imprimió en la pantalla el resultado que se muestra a continuación?

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

Salida:

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

'strtok()' modifica su cadena de argumento terminando tokens con NUL antes de volver. Si intentas examinar todo el búfer (str []) verás que se está modificando entre llamadas sucesivas a 'strtok()'. –

+0

En lugar de ver 'str', mire' str [0] ',' str [1] ',' str [2] ', ... – pmg

+0

@pmg: vi str [0] y str [1] .str [1] debería ser '\ 0', pero era un espacio allí. –

Respuesta

34

strtok() divide la cadena en tokens. es decir, comenzar desde cualquiera de los delimitadores hasta el siguiente sería su único token. En su caso, el token inicial será de "-" y terminará con el siguiente espacio "". Entonces, el próximo token comenzará desde "" y terminará con ",". Aquí obtienes "Esto" como salida. De manera similar, el resto de la cadena se divide en tokens de espacio en espacio y finalmente termina el último token en "."

+0

la condición final para un token se convierte en el token inicial del token siguiente? También hay un carácter nul colocado en el lugar de la condición final? –

+0

@ fahad- Sí, todos los delímetros que tenga serán reemplazados por el carácter NUL como otras personas también han sugerido. –

+0

Si todos los delimitadores son reemplazados por Nul, ¿por qué la cadena contiene "-esto"? Debe contener "\ 0" –

6

La primera vez que la llame, le proporcionan a la cadena tokenize a strtok. Y luego, para obtener los siguientes tokens, simplemente dé NULL a esa función, siempre y cuando devuelva un puntero que no sea NULL.

La función strtok registra la cadena que proporcionó por primera vez cuando la llama. (Que es realmente peligroso para aplicaciones de múltiples hilos)

4

strtok modifica su cadena de entrada. Coloca caracteres nulos ('\ 0') en él para que devuelva bits de la cadena original como tokens. De hecho strtok no asigna memoria. Puede comprenderlo mejor si dibuja la secuencia como una secuencia de cuadros.

141

la función strtok tiempo de ejecución funciona de la siguiente

la primera vez que llame strtok usted proporciona una cadena que desea tokenize

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

en el espacio de cadena anterior parece ser una buena delimitador entre las palabras por lo que permite utilizar lo siguiente:

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

lo que sucede ahora es que 's' se busca hasta que se encuentre el carácter de espacio, se devuelve el primer token ('esto') una dp apunta a esa señal (cadena)

el fin de obtener token siguiente y continuar con la misma cadena nula se pasa como primera argumento ya strtok mantiene un puntero estática a su anterior cadena pasada:

p = strtok(NULL," "); 

p ahora apunta a 'es'

y así sucesivamente hasta que no haya más espacios se pueden encontrar, a continuación, la última cadena se devuelve como el último 'cadena' token.

más convenientemente se podría escribir como este en lugar de imprimir todas las fichas:

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

EDIT:

Si desea almacenar los valores devueltos desde strtok es necesario copiar el token a otro buffer, por ejemplo strdup(p); ya que la cadena original (apuntada por el puntero estático dentro de strtok) se modifica entre iteraciones para devolver el token.

+0

Entonces, ¿realmente no coloca un carácter nul entre la cadena? ¿Por qué mi reloj muestra que la cadena se deja solo con "ESTO"? –

+3

sí reemplaza el '' encontrado con '\ 0'. Y, no restaura '' más tarde, por lo que su cadena se arruinó para siempre. – Arkadiy

+0

Una buena explicación. – Nagaraju

5

strtok convertirá en token una cadena, es decir, la convertirá en una serie de subcadenas.

Lo hace buscando delimitadores que separen estos tokens (o subcadenas). Y especifica los delimitadores. En tu caso, quieres '' o ',' o '.' o '-' para ser el delimitador.

El modelo de programación para extraer estos tokens es que se realiza el strtok de la cadena principal y el conjunto de delimitadores. Luego lo llamas repetidas veces, y cada vez que strtok devuelva el siguiente token que encuentre. Hasta que llega al final de la cadena principal, cuando devuelve un valor nulo. Otra regla es que se pasa la cadena solo la primera vez y NULL para los tiempos subsiguientes. Esta es una forma de decirle a strtok si está comenzando una nueva sesión de tokenización con una nueva cadena, o si está recuperando tokens de una sesión de tokenización previa. Tenga en cuenta que strtok recuerda su estado para la sesión de tokenización. Y, por este motivo, no es reentrante ni seguro para subprocesos (en su lugar, debería usar strtok_r). Otra cosa que debes saber es que realmente modifica la cadena original. Escribe '\ 0' para los delimitadores que encuentra.

Una forma de invocar strtok, sucintamente, es el siguiente:

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

Resultado:

this 
is 
the 
string 
I 
want 
to 
parse 
9

strtok no cambia el propio parámetro (str). Almacena ese puntero (en una variable estática local). A continuación, puede cambiar el parámetro que apunta a en llamadas posteriores sin que se haya pasado el parámetro. (Y se puede adelantar que el puntero se ha mantenido sin embargo se necesita para llevar a cabo sus operaciones.)

Desde la página POSIX strtok:

Esta función utiliza almacenamiento estático para realizar un seguimiento de la posición de la cadena actual entre las llamadas .

hay una variante thread-safe (strtok_r) que no hace este tipo de magia.

+0

¿Cuál fue el razonamiento detrás de esto me pregunto. – chris

+2

Bueno, las funciones de la biblioteca C datan de un tiempo atrás, cuando el enhebrado no aparecía en la imagen (que solo comenzó a existir en 2011 en lo que respecta al estándar C), por lo que la reentrada no era realmente importante (Supongo). Ese local estático hace que la función sea "fácil de usar" (para alguna definición de "fácil"). Como 'ctime' devolviendo una cadena estática, práctica (nadie necesita preguntarse quién debería liberarla), pero no reentrante y te hace tropezar si no estás muy consciente de ello. – Mat

18

strtok mantiene una referencia interna estática que apunta al siguiente token disponible en la cadena; si le pasa un puntero NULL, funcionará desde esa referencia interna.

Esta es la razón por la que strtok no es reentrante; tan pronto como le pasa un nuevo puntero, esa vieja referencia interna es golpeada.

+0

¿A qué te refieres con la referencia interna anterior 'ser golpeado'. ¿Te refieres a 'sobreescrito'? –

+0

@ ylun.ca: sí, eso es lo que quiero decir. –

2

Para entender cómo funciona strtok(), primero hay que saber qué es static variable. This link lo explica bastante bien ...

La clave para el funcionamiento de strtok() es preservar la ubicación del último separador entre llamadas seccessive (por eso strtok() sigue analizar la cadena muy original que se pasa a ella cuando se invoca con un null pointer en llamadas sucesivas). .

tenga una mirada en mi propia strtok() aplicación, llamada zStrtok(), que tiene una funcionalidad sligtly diferente a la proporcionada por 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; 
} 

Y aquí es un ejemplo de uso

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) 

El código es de a string processing library I maintain on Github, llamado zString. Tener un vistazo al código, o incluso contribuir :) https://github.com/fnoyanisi/zString

1

Aquí es mi aplicación que utiliza la tabla hash para el delimitador, lo que significa que O (n) en lugar de 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

Así es como implementé strtok, no es tan genial, pero después de trabajar 2 horas finalmente funcionó. Admite múltiples delimitadores.

#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() almacena el puntero en la variable estática cuando fue la última vez fue apagado, por lo que en su segunda llamada, cuando se pasa la hipótesis nula, strtok() obtiene el puntero de la variable estática.

Si proporciona el mismo nombre de cadena, nuevamente comienza desde el principio.

Además strtok() es destructivo, es decir, realiza cambios en la cadena original. así que asegúrese de tener siempre una copia de la original.

Un problema más de usar strtok() es que almacena la dirección en variables estáticas, en la programación multiproceso llamar a strtok() más de una vez causará un error. Para este uso strtok_r().