¿Cómo strtok () divide la cadena en tokens en C?

114

Explíqueme el funcionamiento de la strtok()función. El manual dice que rompe la cadena en fichas. No puedo entender del manual lo que realmente hace.

Agregué relojes stry *pchpara verificar que funcionaba cuando ocurrió el primer ciclo while, el contenido de strera solo "esto". ¿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:

Dividiendo cadena "- Esto, una cadena de muestra". en tokens:
Esta
una
muestra
cuerda
fuddin
fuente
5
strtok()modifica su cadena de argumentos terminando los tokens con NUL antes de regresar. Si intenta examinar todo el búfer (str []) verá que se modifica entre las sucesivas llamadas a strtok().
Michael Foukarakis
En lugar de ver str, reloj str[0], str[1], str[2], ...
PMG
@pmg: Vi str [0] y str [1] .str [1] debería ser '\ 0', pero había un espacio allí.
fuddin
3
Honestamente, nunca me he molestado en verificar, pero me imagino que almacena el último puntero que pasó, junto con la posición en la que lo dejó. Entonces puede continuar si el puntero es NULL, o borrar la posición y comenzar de nuevo si no es así.
chris
7
@Firegun: variable estática .
DCoder

Respuestas:

38

strtok()divide la cadena en tokens. es decir, comenzar desde cualquiera del delimitador hasta el siguiente sería su única ficha. En su caso, el token inicial será de "-" y terminará con el siguiente espacio "". Luego, el siguiente token comenzará con "" y terminará con ",". Aquí obtienes "Esto" como salida. De manera similar, el resto de la cadena se divide en fichas de espacio en espacio y finalmente termina la última ficha en "."

Sachin Shanbhag
fuente
¿La condición final de un token se convierte en el token inicial del siguiente token? ¿También hay un carácter nulo colocado en el lugar de la condición final?
fuddin
1
@ fahad- Sí, todos los delimitadores que tengas serán reemplazados por el carácter NUL como también han sugerido otras personas.
Sachin Shanbhag
Si todos los delimitadores se reemplazan por Nul, ¿por qué la cadena contiene "-this"? Debe contener "\ 0"
fuddin
2
@fahad: solo reemplaza los caracteres delimitadores con NUL, no todos los caracteres entre delimitadores. Es como dividir la cadena en varios tokens. Obtiene "This" porque está entre dos delimitadores especificados y no "-this".
Sachin Shanbhag
1
@Fahad - Sí, absolutamente. Todos los espacios, "," y "-" se reemplazan por NUL porque los ha especificado como delimitadores, según tengo entendido.
Sachin Shanbhag
212

la función de tiempo de ejecución de strtok funciona así

la primera vez que llama a strtok, proporciona una cadena que desea tokenizar

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

en el espacio de cadena anterior parece ser un buen delimitador entre palabras, así que usemos eso:

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

lo que sucede ahora es que se busca 's' hasta que se encuentra el carácter de espacio, se devuelve el primer token ('esto') yp apunta a ese token (cadena)

para obtener el siguiente token y continuar con la misma cadena, se pasa NULL como primer argumento ya que strtok mantiene un puntero estático a la cadena pasada anterior:

p = strtok(NULL," ");

p ahora apunta a 'es'

y así sucesivamente hasta que no se puedan encontrar más espacios, entonces la última cadena se devuelve como la última 'cadena' del token.

más convenientemente, podría escribirlo así en su lugar para imprimir todos los tokens:

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

EDITAR:

Si desea almacenar los valores devueltos strtok, debe copiar el token a otro búfer, por ejemplo, strdup(p);ya que la cadena original (señalada por el puntero estático en el interior strtok) se modifica entre iteraciones para devolver el token.

AndersK
fuente
Entonces, ¿en realidad no coloca un carácter nulo entre la cadena? ¿Por qué mi reloj muestra que la cadena se deja solo con "ESTO"?
fuddin
4
de hecho, reemplaza el '' que encontró con '\ 0'. Y no se restaura '' más tarde, por lo que su cuerda se arruina para siempre.
33
+1 para búfer estático, esto es lo que no entendí
IEatBagels
1
Un detalle muy importante, que falta en la línea "se devuelve el primer token y papunta a ese token" , es que strtokdebe mutar la cadena original colocando caracteres nulos en lugar de un delimitador (de lo contrario, otras funciones de cadena no sabrían dónde el token termina). Y también realiza un seguimiento del estado mediante una variable estática.
Groo
@Groo Creo que ya lo agregué en el Edit que hice en 2017, pero tienes razón.
AndersK
25

strtokmantiene 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.

Ésta es la razón por la strtokque no vuelve a entrar; tan pronto como le pasa un nuevo puntero, esa vieja referencia interna se golpea.

John Bode
fuente
¿Qué quiere decir con la antigua referencia interna "ser golpeado"? ¿Quiere decir 'sobrescrito'?
ylun.ca
1
@ ylun.ca: sí, a eso me refiero.
John Bode
10

strtokno cambia el parámetro en sí ( str). Almacena ese puntero (en una variable estática local). A continuación, puede cambiar a qué apunta ese parámetro en llamadas posteriores sin que se devuelva el parámetro. (Y puede hacer avanzar ese puntero que ha mantenido sin embargo necesita realizar sus operaciones).

Desde la strtokpágina POSIX :

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

Existe una variante segura para subprocesos ( strtok_r) que no hace este tipo de magia.

Estera
fuente
2
Bueno, las funciones de la biblioteca C datan de hace mucho tiempo, cuando el subproceso no estaba en absoluto 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 ctimedevolver una cadena estática: práctica (nadie debe preguntarse quién debería liberarla), pero no reentrar y hacer tropezar si no está muy consciente de ello.
Mat
Esto está mal: " strtokno cambia el parámetro en sí ( str)". puts(str);imprime "- Esto" desde que se strtokmodificó str.
MarredCheese
1
@MarredCheese: vuelve a leer. No modifica el puntero. Modifica los datos a los que apunta el puntero (es decir, los datos de la cadena)
Mat
Oh está bien, no me di cuenta de que es a lo que quieres llegar. Convenido.
MarredCheese
8

La primera vez que lo llama, proporciona la cadena para tokenizar strtok. Y luego, para obtener los siguientes tokens, simplemente le da NULLa esa función, siempre que devuelva un no NULLpuntero.

La strtokfunción registra la cadena que proporcionó por primera vez cuando la llamó. (Lo que es realmente peligroso para aplicaciones multiproceso)

tibur
fuente
8

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

Lo hace buscando delimitadores que separan estos tokens (o subcadenas). Y especificas los delimitadores. En su caso, quiere '' o ',' o '.' o '-' para ser el delimitador.

El modelo de programación para extraer estos tokens es que usted entregue su cadena principal y el conjunto de delimitadores. Luego lo llama repetidamente, y cada vez que strtok devolverá el siguiente token que encuentre. Hasta que llega al final de la cadena principal, cuando devuelve un valor nulo. Otra regla es que pasa la cadena solo la primera vez y NULL para las veces siguientes. Esta es una forma de decirle a strtok si está iniciando una nueva sesión de tokenización con una nueva cadena o si está recuperando tokens de una sesión de tokenización anterior. Tenga en cuenta que strtok recuerda su estado para la sesión de tokenización. Y por esta razón no es reentrante ni seguro para subprocesos (debería usar strtok_r en su lugar). Otra cosa que debe saber es que en realidad modifica la cadena original. Escribe '\ 0' para los delimitadores que encuentra.

Una forma de invocar strtok, de manera sucinta, es la 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
Ziffusion
fuente
5

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 cadena como una secuencia de cuadros.

xpmatteo
fuente
3

Para comprender cómo strtok()funciona, primero es necesario saber qué es una variable estática . Este enlace lo explica bastante bien ...

La clave para la operación de strtok()es preservar la ubicación del último separador entre llamadas seccesivas (es por eso que strtok()continúa analizando la cadena muy original que se le pasa cuando se invoca con una null pointeren llamadas sucesivas).

Eche un vistazo a mi propia strtok()implementación, llamada zStrtok(), que tiene una funcionalidad ligeramente diferente a la proporcionada porstrtok()

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í hay 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 una biblioteca de procesamiento de cadenas que mantengo en Github , llamada zString. Eche un vistazo al código, o incluso contribuya :) https://github.com/fnoyanisi/zString

fnisi
fuente
3

Así es como implementé strtok, no tan bueno, pero después de trabajar 2 horas finalmente funcionó. Soporta 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;
}
Dipak
fuente
1

Aquí está mi implementación que usa una tabla hash para el delimitador, lo que significa que O (n) en lugar de O (n ^ 2) (aquí hay un enlace al código) :

#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
fuente
1

strtok () almacena el puntero en la variable estática donde lo dejó la última vez, por lo que en su segunda llamada, cuando pasamos el nulo, 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 del original.

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

Vaibhav
fuente
0

Para aquellos que todavía tienen dificultades para entender esta strtok()función, echen un vistazo a este ejemplo de pythontutor , es una gran herramienta para visualizar su código C (o C ++, Python ...).

En caso de que el enlace se rompa, pegue:

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

Los créditos van para Anders K.


fuente
0

puede escanear la matriz de caracteres en busca del token si lo encontró, simplemente imprima una nueva línea o imprima el carácter.

#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
fuente
0

Por lo tanto, este es un fragmento de código para ayudar a comprender mejor este tema.

Tokens de impresión

Tarea: Dada una oración, s, imprima cada palabra de la oración en una nueva línea.

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

Entrada: How is that

Resultado:

How
is
that

Explicación: Entonces, aquí, se usa la función "strtok ()" y se itera usando el bucle for para imprimir los tokens en líneas separadas.

La función tomará parámetros como 'cadena' y 'punto de ruptura' y romperá la cadena en esos puntos de ruptura y formará tokens. Ahora, esos tokens se almacenan en 'p' y se usan más para imprimir.

tr_abhishek
fuente
Creo que explicarlo con un ejemplo es mucho mejor que hacer referencia a algún documento.
tr_abhishek