Wie teilt strtok () die Zeichenfolge in C in Token auf?

114

Bitte erklären Sie mir die strtok()Funktionsweise. Das Handbuch sagt, dass es die Zeichenfolge in Token zerlegt. Ich kann aus dem Handbuch nicht verstehen, was es tatsächlich tut.

Ich habe Uhren hinzugefügt strund um *pchzu überprüfen, ob es funktioniert, als die erste while-Schleife auftrat, war der Inhalt von strnur "this". Wie wurde die unten gezeigte Ausgabe auf dem Bildschirm gedruckt?

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

Ausgabe:

Teilungszeichenfolge "- Dies ist eine Beispielzeichenfolge." in Token:
Dies
ein
Stichprobe
Zeichenfolge
Fuddin
quelle
5
strtok()Ändert die Argumentzeichenfolge, indem Token vor der Rückkehr mit NUL beendet werden. Wenn Sie versuchen, den gesamten Puffer (str []) zu untersuchen, wird er zwischen aufeinanderfolgenden Aufrufen von geändert strtok().
Michael Foukarakis
Anstatt zu beobachten str, Uhr str[0], str[1], str[2], ...
PMG
@pmg: Ich habe gesehen, dass str [0] und str [1] .str [1] '\ 0' sein sollten, aber es war ein Leerzeichen dort.
Fuddin
3
Ehrlich gesagt habe ich mich nie darum gekümmert, es zu überprüfen, aber ich stelle mir vor, es speichert den zuletzt übergebenen Zeiger zusammen mit der Position, an der er aufgehört hat. Dann kann es einfach fortgesetzt werden, wenn der Zeiger NULL ist, oder die Position löschen und von vorne beginnen, wenn nicht.
Chris
7
@Firegun: statische Variable .
DCoder

Antworten:

38

strtok()teilt die Zeichenfolge in Token. Das heißt, wenn Sie von einem der Begrenzer zum nächsten beginnen, ist dies Ihr einziger Token. In Ihrem Fall ist der Start-Token von "-" und endet mit dem nächsten Leerzeichen "". Dann beginnt der nächste Token mit "" und endet mit ",". Hier erhalten Sie "This" als Ausgabe. In ähnlicher Weise wird der Rest der Zeichenfolge von Raum zu Raum in Token aufgeteilt und schließlich der letzte Token auf "" beendet.

Sachin Shanbhag
quelle
Die Endbedingung für ein Token wird zum Start-Token des nächsten Tokens. Gibt es auch ein Nullzeichen anstelle der Endbedingung?
Fuddin
1
@ fahad- Ja, alle Begrenzer, die Sie haben, werden durch NUL-Zeichen ersetzt, wie andere Leute ebenfalls vorgeschlagen haben.
Sachin Shanbhag
Wenn alle Trennzeichen durch Nul ersetzt werden, warum enthält die Zeichenfolge dann "-this"? Es sollte "\ 0" enthalten
fuddin
2
@fahad - Ersetzt nur die Trennzeichen durch NUL, nicht alle Zeichen zwischen Trennzeichen. Es ist eine Art, die Zeichenfolge in mehrere Token aufzuteilen. Sie erhalten "This", weil es zwischen zwei angegebenen Trennzeichen und nicht zwischen "-this" liegt.
Sachin Shanbhag
1
@Fahad - Ja, absolut. Alle Leerzeichen "," und "-" werden durch NUL ersetzt, da Sie diese meines Wissens als Trennzeichen angegeben haben.
Sachin Shanbhag
212

Die strtok-Laufzeitfunktion funktioniert folgendermaßen

Wenn Sie strtok zum ersten Mal aufrufen, geben Sie eine Zeichenfolge an, die Sie tokenisieren möchten

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

In der obigen Zeichenfolge scheint der Leerzeichen ein gutes Trennzeichen zwischen den Wörtern zu sein. Verwenden wir also Folgendes:

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

Was jetzt passiert ist, dass 's' durchsucht wird, bis das Leerzeichen gefunden ist, das erste Token zurückgegeben wird ('this') und p auf dieses Token zeigt (Zeichenfolge)

Um das nächste Token zu erhalten und mit derselben Zeichenfolge fortzufahren, wird NULL als erstes Argument übergeben, da strtok einen statischen Zeiger auf Ihre zuvor übergebene Zeichenfolge beibehält :

p = strtok(NULL," ");

p zeigt jetzt auf 'ist'

usw., bis keine Leerzeichen mehr gefunden werden können, wird die letzte Zeichenfolge als letzte Token-Zeichenfolge zurückgegeben.

bequemer können Sie es stattdessen so schreiben, um alle Token auszudrucken:

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

BEARBEITEN:

Wenn Sie die zurückgegebenen Werte speichern möchten, strtokmüssen Sie das Token in einen anderen Puffer kopieren, z. B. strdup(p);da die ursprüngliche Zeichenfolge (auf die der statische Zeiger im Inneren zeigt strtok) zwischen den Iterationen geändert wird, um das Token zurückzugeben.

AndersK
quelle
Warum wird also kein Nullzeichen zwischen die Zeichenfolge eingefügt? Warum zeigt meine Uhr, dass die Zeichenfolge nur mit "THIS" belassen wird?
Fuddin
4
es ersetzt tatsächlich das gefundene '' durch '\ 0'. Und es wird später nicht wiederhergestellt, sodass Ihre Zeichenfolge endgültig ruiniert ist.
33
+1 für statischen Puffer, das habe ich nicht verstanden
IEatBagels
1
Ein sehr wichtiges Detail, das in der Zeile "Das erste Token wird zurückgegeben und pzeigt auf dieses Token" fehlt , besteht darin, dass strtokdie ursprüngliche Zeichenfolge durch Platzieren von Nullzeichen anstelle eines Trennzeichens mutiert werden muss (andernfalls würden andere Zeichenfolgenfunktionen nicht wissen, wo der Token endet). Außerdem wird der Status mithilfe einer statischen Variablen verfolgt.
Groo
@Groo Ich denke, ich habe das bereits in der Bearbeitung hinzugefügt, die ich 2017 gemacht habe, aber du hast recht.
AndersK
25

strtokverwaltet eine statische interne Referenz, die auf das nächste verfügbare Token in der Zeichenfolge verweist; Wenn Sie einen NULL-Zeiger übergeben, funktioniert dies anhand dieser internen Referenz.

Dies ist der Grund, warum Sie strtoknicht wieder eintreten. Sobald Sie einen neuen Zeiger übergeben, wird diese alte interne Referenz blockiert.

John Bode
quelle
Was meinst du mit der alten internen Referenz "Überfallen werden"? Meinst du "überschrieben"?
ylun.ca
1
@ ylun.ca: ja, das meine ich.
John Bode
10

strtokändert den Parameter selbst nicht ( str). Dieser Zeiger wird gespeichert (in einer lokalen statischen Variablen). Es kann dann ändern , was dass Parameterpunkte in nachfolgenden Aufrufen , ohne den Parameter mit zurück übergeben. (Und es kann den Zeiger vorrücken, den es behalten hat, jedoch muss es seine Operationen ausführen.)

Von der POSIX- strtokSeite:

Diese Funktion verwendet statischen Speicher, um die aktuelle Zeichenfolgenposition zwischen Aufrufen zu verfolgen.

Es gibt eine thread-sichere Variante ( strtok_r), die diese Art von Magie nicht ausführt.

Matte
quelle
2
Nun, die Funktionen der C-Bibliothek stammen aus früheren Zeiten, als das Threading überhaupt nicht im Bilde war (das erst 2011 in Bezug auf den C-Standard existierte), so dass der Wiedereintritt nicht wirklich wichtig war ( Ich vermute). Dieses statische Lokal macht die Funktion "einfach zu bedienen" (für eine Definition von "einfach"). Wie das ctimeZurückgeben einer statischen Zeichenfolge - praktisch (niemand muss sich fragen, wer sie freigeben soll), aber nicht erneut eintreten und Sie stolpern, wenn Sie sich dessen nicht sehr bewusst sind.
Mat
Das ist falsch: " strtokÄndert den Parameter selbst nicht ( str)." puts(str);druckt "- This" seitdem strtokgeändert str.
MarredCheese
1
@MarredCheese: nochmal lesen. Der Zeiger wird nicht geändert. Es ändert die Daten, auf die der Zeiger zeigt (dh die Zeichenfolgendaten)
Mat
Oh ok, ich wusste nicht, dass du das willst. Einverstanden.
MarredCheese
8

Wenn Sie es zum ersten Mal aufrufen, geben Sie die Zeichenfolge an, für die ein Token erstellt werden soll strtok. Um die folgenden Token zu erhalten, geben Sie NULLdiese Funktion einfach an, solange sie einen Nicht- NULLZeiger zurückgibt .

Die strtokFunktion zeichnet die Zeichenfolge auf, die Sie beim Aufrufen zuerst angegeben haben. (Was für Multithread-Anwendungen wirklich gefährlich ist)

Tibur
quelle
8

strtok wird einen String tokenisieren, dh in eine Reihe von Teilzeichenfolgen konvertieren.

Dazu werden Trennzeichen gesucht, die diese Token (oder Teilzeichenfolgen) trennen. Und Sie geben die Trennzeichen an. In Ihrem Fall möchten Sie '' oder ',' oder '.' oder '-' als Trennzeichen.

Das Programmiermodell zum Extrahieren dieser Token besteht darin, dass Sie Ihre Hauptzeichenfolge und die Trennzeichen mit der Hand strtok. Dann rufen Sie es wiederholt auf und jedes Mal gibt strtok das nächste gefundene Token zurück. Bis es das Ende der Hauptzeichenfolge erreicht, wenn es eine Null zurückgibt. Eine andere Regel ist, dass Sie die Zeichenfolge nur beim ersten Mal und NULL für die folgenden Male übergeben. Auf diese Weise können Sie strtok mitteilen, ob Sie eine neue Tokenisierungssitzung mit einer neuen Zeichenfolge starten oder Token aus einer vorherigen Tokenisierungssitzung abrufen. Beachten Sie, dass sich strtok seinen Status für die Tokenisierungssitzung merkt. Aus diesem Grund ist es nicht wiedereintritts- oder threadsicher (Sie sollten stattdessen strtok_r verwenden). Eine andere Sache zu wissen ist, dass es tatsächlich die ursprüngliche Zeichenfolge ändert. Es schreibt '\ 0' für die Trennzeichen, die es findet.

Eine Möglichkeit, strtok kurz und bündig aufzurufen, ist folgende:

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

Ergebnis:

this
is
the
string
I
want
to
parse
Ziffusion
quelle
5

strtok ändert seine Eingabezeichenfolge. Es werden Nullzeichen ('\ 0') eingefügt, sodass Bits der ursprünglichen Zeichenfolge als Token zurückgegeben werden. Tatsächlich weist strtok keinen Speicher zu. Sie können es besser verstehen, wenn Sie die Zeichenfolge als eine Folge von Feldern zeichnen.

xpmatteo
quelle
3

Um zu verstehen, wie es strtok()funktioniert, muss man zuerst wissen, was eine statische Variable ist. Dieser Link erklärt es ganz gut ....

Der Schlüssel für die Operation von strtok()besteht darin, den Speicherort des letzten Trennzeichens zwischen aufeinanderfolgenden Aufrufen beizubehalten (aus diesem Grund wird strtok()weiterhin die sehr ursprüngliche Zeichenfolge analysiert, die an sie übergeben wird, wenn sie mit einem null pointerin aufeinanderfolgenden Aufrufen aufgerufen wird ).

Schau dir meine an strtok() Implementierung an, zStrtok()die eine etwas andere Funktionalität hat als die vonstrtok()

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

Und hier ist ein Beispiel für die Verwendung

  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)

Der Code stammt aus einer String-Verarbeitungsbibliothek, die ich auf Github verwalte und die zString heißt. Schauen Sie sich den Code an oder tragen Sie sogar dazu bei :) https://github.com/fnoyanisi/zString

fnisi
quelle
3

So habe ich strtok implementiert. Nicht so toll, aber nach 2 Stunden Arbeit hat es endlich funktioniert. Es werden mehrere Trennzeichen unterstützt.

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

Hier ist meine Implementierung, die eine Hash-Tabelle für das Trennzeichen verwendet, was bedeutet, dass es O (n) anstelle von O (n ^ 2) ist (hier ist ein Link zum 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;
}
Kohn1001
quelle
1

strtok () speichert den Zeiger in einer statischen Variablen, in der Sie zuletzt aufgehört haben. Wenn wir also beim zweiten Aufruf die Null übergeben, erhält strtok () den Zeiger von der statischen Variablen.

Wenn Sie denselben Zeichenfolgennamen angeben, beginnt er erneut von vorne.

Darüber hinaus ist strtok () destruktiv, dh es werden Änderungen an der ursprünglichen Zeichenfolge vorgenommen. Stellen Sie also sicher, dass Sie immer eine Kopie des Originals haben.

Ein weiteres Problem bei der Verwendung von strtok () besteht darin, dass beim Speichern der Adresse in statischen Variablen bei der Multithread-Programmierung das mehrmalige Aufrufen von strtok () einen Fehler verursacht. Verwenden Sie dazu strtok_r ().

Vaibhav
quelle
0

Wer diese strtok()Funktion noch nicht richtig versteht , kann sich dieses Pythonontutor-Beispiel ansehen . Es ist ein großartiges Tool zur Visualisierung Ihres C-Codes (oder C ++, Python ...).

Falls der Link unterbrochen wurde, fügen Sie Folgendes ein:

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

Credits gehen an Anders K.


quelle
0

Sie können das char-Array nach dem Token durchsuchen, wenn Sie festgestellt haben, dass es nur eine neue Zeile druckt. Andernfalls drucken Sie das 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
quelle
0

Dies ist also ein Code-Snippet, um dieses Thema besser zu verstehen.

Token drucken

Aufgabe: Geben Sie bei einem gegebenen Satz s jedes Wort des Satzes in einer neuen Zeile aus.

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

Eingang: How is that

Ergebnis:

How
is
that

Erläuterung: Hier wird also die Funktion "strtok ()" verwendet und mit der for-Schleife iteriert, um die Token in separaten Zeilen zu drucken.

Die Funktion verwendet Parameter wie 'Zeichenfolge' und 'Haltepunkt' und bricht die Zeichenfolge an diesen Haltepunkten und bildet Token. Diese Token werden nun in 'p' gespeichert und zum Drucken weiter verwendet.

tr_abhishek
quelle
Ich denke, das Erklären anhand eines Beispiels ist viel besser als das Verweisen auf ein Dokument.
tr_abhishek