Como strtok () divide a string em tokens em C?

114

Por favor, me explique o funcionamento da strtok()função. O manual diz que divide a string em tokens. Não consigo entender com base no manual o que ele realmente faz.

Eu adicionei watch on stre *pchpara verificar seu funcionamento quando o primeiro loop while ocorreu, o conteúdo de strera apenas "this". Como a saída mostrada abaixo foi impressa na tela?

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

Resultado:

Dividindo string "- Este, um string de amostra." em tokens:
este
uma
amostra
corda
fuddin
fonte
5
strtok()modifica sua string de argumento encerrando tokens com NUL antes de retornar. Se você tentar examinar todo o buffer (str []), você o verá sendo modificado entre chamadas sucessivas de strtok().
Michael Foukarakis,
Em vez de assistir str, relógio str[0], str[1], str[2], ...
pmg
@pmg: Eu assisti str [0] e str [1] .str [1] deveria ser '\ 0', mas havia um espaço ali.
fuddin
3
Honestamente, nunca me preocupei em verificar, mas imagino que ele armazene o último ponteiro passado, junto com a posição em que parou. Em seguida, pode simplesmente continuar se o ponteiro for NULL ou limpar a posição e começar de novo se não for.
chris de
7
@Firegun: variável estática .
DCoder

Respostas:

38

strtok()divide a string em tokens. ou seja, começar de qualquer um dos delimitadores para o próximo seria seu único token. No seu caso, o token inicial será de "-" e terminará com o próximo espaço "". O próximo token começará em "" e terminará com ",". Aqui você obtém "This" como saída. Da mesma forma, o resto da string é dividido em tokens de espaço para espaço e, finalmente, terminando o último token em "."

Sachin Shanbhag
fonte
a condição final para um token torna-se o token inicial do próximo token? também há um caractere nul colocado no lugar da condição final?
fuddin
1
@ fahad- Sim, todos os delimitadores que você tiver serão substituídos por caracteres NUL, como outras pessoas também sugeriram.
Sachin Shanbhag
Se todos os delimitadores são substituídos por Nul, então por que a string contém "-este"? Deve conter "\ 0"
fuddin
2
@fahad - Substitui apenas os caracteres delimitadores por NUL, nem todos os caracteres entre os delimitadores. É uma espécie de divisão da string em vários tokens. Você obtém "This" porque está entre dois delimitadores especificados e não "-this".
Sachin Shanbhag,
1
@Fahad - Sim, absolutamente. Todos os espaços, "," e "-" são substituídos por NUL porque você os especificou como delimitadores, pelo que entendi.
Sachin Shanbhag,
212

a função de tempo de execução strtok funciona assim

na primeira vez que você chama strtok, você fornece uma string que deseja tokenizar

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

no espaço da string acima parece ser um bom delimitador entre as palavras, então vamos usar isso:

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

o que acontece agora é que 's' é pesquisado até que o caractere de espaço seja encontrado, o primeiro token é retornado ('this') e p aponta para esse token (string)

a fim de obter o próximo token e continuar com a mesma string, NULL é passado como primeiro argumento, pois strtok mantém um ponteiro estático para a string passada anterior:

p = strtok(NULL," ");

p agora aponta para 'é'

e assim por diante até que nenhum outro espaço seja encontrado, então a última string é retornada como o último token 'string'.

mais convenientemente, você poderia escrever assim em vez de imprimir todos os tokens:

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

EDITAR:

Se você deseja armazenar os valores retornados, strtokvocê precisa copiar o token para outro buffer, por exemplo, strdup(p);já que a string original (apontada pelo ponteiro estático dentro strtok) é modificada entre as iterações para retornar o token.

AndersK
fonte
Portanto, na verdade, ele não coloca um caractere nulo entre a string? Por que meu relógio mostra que a string ficou apenas com "ISTO"?
fuddin
4
ele realmente substitui o '' encontrado por '\ 0'. E não restaura '' mais tarde, então sua string está arruinada para sempre.
33
+1 para buffer estático, isto é o que eu não entendi
IEatBagels
1
Um detalhe muito importante, ausente da linha "o primeiro token é retornado e paponta para aquele token" , é que strtokprecisa alterar a string original colocando um caractere nulo no lugar de um delimitador (caso contrário, outras funções de string não saberiam onde o token termina). E também rastreia o estado usando uma variável estática.
Groo
@Groo Acho que já adicionei isso na edição que fiz em 2017, mas você tem razão.
AndersK
25

strtokmantém uma referência interna estática apontando para o próximo token disponível na string; se você passar um ponteiro NULL, ele funcionará a partir dessa referência interna.

Esta é a razão pela qual strtoknão é reentrante; assim que você passa um novo ponteiro, aquela referência interna antiga é eliminada.

John Bode
fonte
O que você quer dizer com a velha referência interna 'sendo derrotado'. Você quer dizer 'sobrescrito'?
ylun.ca
1
@ ylun.ca: sim, é isso que quero dizer.
John Bode,
10

strtoknão muda o próprio parâmetro ( str). Ele armazena esse ponteiro (em uma variável estática local). Ele pode então alterar o que esse parâmetro aponta nas chamadas subsequentes sem que o parâmetro seja passado de volta. (E ele pode avançar esse ponteiro que manteve, da maneira que for necessário para realizar suas operações.)

Na strtokpágina POSIX :

Esta função usa armazenamento estático para controlar a posição atual da string entre as chamadas.

Existe uma variante thread-safe ( strtok_r) que não faz esse tipo de mágica.

Esteira
fonte
2
Bem, as funções da biblioteca C datam de muito tempo atrás, quando o threading não estava em cena (que só começou a existir em 2011 no que diz respeito ao padrão C), então a reentrada não era realmente importante ( Eu acho). Esse local estático torna a função "fácil de usar" (para alguma definição de "fácil"). É como ctimeretornar uma string estática - prática (ninguém precisa se perguntar quem deve liberá-la), mas não reentrar e tropeçar em você se você não estiver muito ciente disso.
Mat
Isso está errado: " strtoknão altera o próprio parâmetro ( str)." puts(str);imprime "- This" desde strtokmodificado str.
MarredCheese
1
@MarredCheese: leia novamente. Não modifica o ponteiro. Ele modifica os dados para os quais o ponteiro aponta (ou seja, os dados da string)
Mat
Oh ok, eu não percebi que é isso que você quer chegar. Acordado.
MarredCheese
8

Na primeira vez que você chama, você fornece a string para tokenizar strtok. E então, para obter os seguintes tokens, basta dar NULLa essa função, desde que ela retorne um não NULLponteiro.

A strtokfunção registra a string que você forneceu primeiro ao chamá-la. (O que é realmente perigoso para aplicativos multi-thread)

Tibur
fonte
8

strtok irá tokenizar uma string, isto é, convertê-la em uma série de substrings.

Ele faz isso procurando por delimitadores que separam esses tokens (ou substrings). E você especifica os delimitadores. No seu caso, você quer '' ou ',' ou '.' ou '-' para ser o delimitador.

O modelo de programação para extrair esses tokens é que você entrega ao strtok sua string principal e o conjunto de delimitadores. Em seguida, você o chama repetidamente e, a cada vez, strtok retornará o próximo token que encontrar. Até atingir o final da string principal, quando retorna um nulo. Outra regra é que você passe a string apenas na primeira vez e NULL nas vezes subsequentes. Esta é uma maneira de dizer a strtok se você está iniciando uma nova sessão de tokenização com uma nova string ou se está recuperando tokens de uma sessão de tokenização anterior. Observe que strtok lembra seu estado para a sessão de tokenização. E por esta razão não é reentrante ou thread-safe (você deve usar strtok_r ao invés). Outra coisa a saber é que ele realmente modifica a string original. Ele escreve '\ 0' para os delimitadores que encontrar.

Uma maneira de invocar strtok, sucintamente, é a seguinte:

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
fonte
5

strtok modifica sua string de entrada. Ele coloca caracteres nulos ('\ 0') nele para que retorne bits da string original como tokens. Na verdade, strtok não aloca memória. Você pode entender melhor se desenhar o barbante como uma sequência de caixas.

xpmatteo
fonte
3

Para entender como strtok()funciona, primeiro é necessário saber o que é uma variável estática . Este link explica muito bem ....

A chave para a operação de strtok()é preservar a localização do último separador entre chamadas seccessivas (é por isso que strtok()continua a analisar a string original que é passada para ele quando é invocado com null pointerchamadas sucessivas).

Dê uma olhada em minha própria strtok()implementação, chamada zStrtok(), que tem uma funcionalidade ligeiramente diferente da fornecida 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;
}

E aqui está um exemplo 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)

O código é de uma biblioteca de processamento de strings que mantenho no Github , chamada zString. Dê uma olhada no código ou até mesmo contribua :) https://github.com/fnoyanisi/zString

fnisi
fonte
3

Foi assim que implementei o strtok, não muito bom, mas depois de trabalhar 2 horas nele, finalmente consegui funcionar. Ele oferece suporte a vários 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
fonte
1

Aqui está minha implementação que usa uma tabela hash para o delimitador, o que significa O (n) em vez de O (n ^ 2) (aqui está um link para o 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
fonte
1

strtok () armazena o ponteiro na variável estática onde você parou da última vez, então em sua segunda chamada, quando passamos o nulo, strtok () obtém o ponteiro da variável estática.

Se você fornecer o mesmo nome de string, ele começará novamente do início.

Além disso, strtok () é destrutivo, ou seja, faz alterações na string original. portanto, certifique-se de sempre ter uma cópia do original.

Outro problema de usar strtok () é que, como ele armazena o endereço em variáveis ​​estáticas, na programação multithread chamar strtok () mais de uma vez causará um erro. Para isso, use strtok_r ().

Vaibhav
fonte
0

Para aqueles que ainda estão com dificuldades para entender esta strtok()função, dê uma olhada neste exemplo do pythontutor , é uma ótima ferramenta para visualizar seu código C (ou C ++, Python ...).

Caso o link tenha quebrado, cole:

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

Os créditos vão para Anders K.


fonte
0

você pode escanear o array char procurando pelo token se você o encontrou apenas imprima uma nova linha senão imprima o char.

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

Portanto, este é um trecho de código para ajudar a entender melhor este tópico.

Impressão de tokens

Tarefa: dada uma frase, s, imprima cada palavra da frase em uma nova linha.

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

Explicação: Então, aqui, a função "strtok ()" é usada e iterada usando o loop for para imprimir os tokens em linhas separadas.

A função pegará parâmetros como 'string' e 'break-point' e quebrará a string nesses pontos de quebra e formará tokens. Agora, esses tokens são armazenados em 'p' e são usados ​​posteriormente para impressão.

tr_abhishek
fonte
Acho que explicar por meio de um exemplo é muito melhor do que se referir a algum documento.
tr_abhishek