Bagaimana strtok () membagi string menjadi token di C?

114

Tolong jelaskan kepada saya cara kerja strtok()fungsinya. Manual mengatakan itu memecah string menjadi token. Saya tidak dapat memahami dari manual apa yang sebenarnya dilakukannya.

Saya menambahkan jam tangan strdan *pchmemeriksa kerjanya ketika loop sementara pertama terjadi, isinya strhanya "ini". Bagaimana keluaran yang ditunjukkan di bawah ini tercetak di layar?

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

Keluaran:

Memisahkan string "- Ini, string sampel." menjadi token:
Ini
Sebuah
Sampel
tali
fuddin
sumber
5
strtok()mengubah string argumennya dengan menghentikan token dengan NUL sebelum kembali. Jika Anda mencoba untuk memeriksa seluruh buffer (str []) Anda akan melihatnya diubah antara panggilan yang berurutan ke strtok().
Michael Foukarakis
Alih-alih menonton str, menonton str[0], str[1], str[2], ...
PMG
@pmg: Saya melihat str [0] dan str [1] .str [1] harus '\ 0', tapi ada spasi di sana.
fuddin
3
Sejujurnya saya tidak pernah repot-repot memeriksanya, tetapi saya membayangkan itu menyimpan penunjuk terakhir yang masuk, bersama dengan posisi yang ditinggalkannya. Kemudian dapat dilanjutkan jika penunjuk adalah NULL, atau hapus posisi dan mulai kembali jika tidak.
chris
7
@Firegun: variabel statis .
DCoder

Jawaban:

38

strtok()membagi string menjadi token. yaitu mulai dari salah satu pembatas ke pembatas berikutnya akan menjadi satu token Anda. Dalam kasus Anda, token awal adalah dari "-" dan diakhiri dengan spasi "". Kemudian token berikutnya akan dimulai dari "" dan diakhiri dengan ",". Di sini Anda mendapatkan "Ini" sebagai output. Demikian pula, sisa string dipecah menjadi token dari ruang ke ruang dan akhirnya mengakhiri token terakhir pada "."

Sachin Shanbhag
sumber
kondisi akhir untuk satu token menjadi token awal dari token berikutnya? juga apakah ada karakter nol yang ditempatkan di tempat kondisi akhir?
fuddin
1
@ fahad- Ya, semua delimeter yang Anda miliki akan diganti dengan karakter NUL seperti yang juga disarankan orang lain.
Sachin Shanbhag
Jika semua pembatas diganti dengan Nul, mengapa string mengandung "-ini"? Ini harus berisi "\ 0"
fuddin
2
@fahad - Ini hanya mengganti karakter pemisah dengan NUL, tidak semua karakter di antara pemisah. Jenisnya membagi string menjadi beberapa token. Anda mendapatkan "Ini" karena berada di antara dua pembatas yang ditentukan dan bukan "-ini".
Sachin Shanbhag
1
@Fahad - Ya, tentu saja. Semua spasi, "," dan "-" diganti dengan NUL karena Anda telah menentukannya sebagai pembatas, sejauh yang saya mengerti.
Sachin Shanbhag
212

fungsi runtime strtok bekerja seperti ini

pertama kali Anda memanggil strtok, Anda memberikan string yang ingin Anda tokenize

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

di ruang string di atas tampaknya menjadi pembatas yang baik antara kata-kata jadi mari kita gunakan itu:

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

apa yang terjadi sekarang adalah bahwa 's' dicari sampai karakter spasi ditemukan, token pertama dikembalikan ('ini') dan p menunjuk ke token itu (string)

untuk mendapatkan token berikutnya dan melanjutkan dengan string yang sama NULL dilewatkan sebagai argumen pertama karena strtok mempertahankan pointer statis ke string yang Anda lewati sebelumnya:

p = strtok(NULL," ");

p sekarang menunjuk ke 'adalah'

dan seterusnya sampai tidak ada lagi spasi yang ditemukan, maka string terakhir dikembalikan sebagai 'string' token terakhir.

lebih mudahnya Anda bisa menulisnya seperti ini daripada mencetak semua token:

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

EDIT:

Jika Anda ingin menyimpan nilai yang dikembalikan dari strtokAnda perlu menyalin token ke buffer lain misalnya strdup(p);karena string asli (yang ditunjukkan oleh pointer statis di dalamnya strtok) dimodifikasi di antara iterasi untuk mengembalikan token.

AndersK
sumber
Jadi tidak benar-benar menempatkan karakter nul di antara string? Mengapa jam tangan saya menunjukkan bahwa string hanya tersisa "INI"?
fuddin
4
itu memang menggantikan '' yang ditemukan dengan '\ 0'. Dan, itu tidak mengembalikan '' nanti, jadi string Anda rusak untuk selamanya.
33
1 untuk buffer statis, ini yang saya tidak mengerti
IEatBagels
1
Detail yang sangat penting, hilang dari baris "token pertama dikembalikan dan pmenunjuk ke token itu" , adalah strtokkebutuhan untuk mengubah string asli dengan menempatkan karakter nol sebagai pengganti pemisah (jika tidak, fungsi string lain tidak akan tahu di mana token berakhir). Dan itu juga melacak keadaan menggunakan variabel statis.
Groo
@Groo Saya rasa saya sudah menambahkannya di Edit yang saya lakukan pada tahun 2017, tetapi Anda benar.
AndersK
25

strtokmemelihara referensi internal statis yang menunjuk ke token yang tersedia berikutnya dalam string; jika Anda memberikan pointer NULL, itu akan bekerja dari referensi internal itu.

Inilah alasannya strtokbukan peserta kembali; segera setelah Anda memberikan pointer baru, referensi internal lama tersebut akan terhalang.

John Bode
sumber
Apa yang Anda maksud dengan referensi internal lama 'dipukuli'. Apakah maksud Anda 'ditimpa'?
ylun.ca
1
@ ylun.ca: ya, itu yang saya maksud.
John Bode
10

strtoktidak mengubah parameter itu sendiri ( str). Ini menyimpan pointer itu (dalam variabel statis lokal). Hal ini kemudian dapat mengubah apa yang parameter poin ke dalam panggilan berikutnya tanpa parameter berlalu kembali. (Dan itu dapat memajukan penunjuk yang telah disimpannya bagaimanapun ia perlu melakukan operasinya.)

Dari strtokhalaman POSIX :

Fungsi ini menggunakan penyimpanan statis untuk melacak posisi string saat ini di antara panggilan.

Ada varian aman utas ( strtok_r) yang tidak melakukan sihir jenis ini.

Tikar
sumber
2
Nah, fungsi perpustakaan C berasal dari masa lalu ketika, threading tidak ada dalam gambar sama sekali (yang baru mulai ada pada tahun 2011 sejauh menyangkut standar C), jadi masuk kembali tidak terlalu penting ( Saya kira). Lokal statis itu membuat fungsi "mudah digunakan" (untuk beberapa definisi "mudah"). Seperti ctimemengembalikan string statis - praktis (tidak ada yang perlu bertanya-tanya siapa yang harus membebaskannya), tetapi tidak memasukkan kembali dan membuat Anda tersandung jika Anda tidak terlalu menyadarinya.
Mat
Ini salah: " strtoktidak mengubah parameter itu sendiri ( str)." puts(str);mencetak "- Ini" sejak strtokdiubah str.
MarredCheese
1
@MarredCheese: baca lagi. Itu tidak mengubah penunjuk. Ini memodifikasi data yang ditunjuk penunjuk (yaitu data string)
Mat
Oh oke, saya tidak menyadari itulah yang Anda maksud. Sepakat.
MarredCheese
8

Pertama kali Anda memanggilnya, Anda memberikan string untuk dijadikan token strtok. Dan kemudian, untuk mendapatkan token berikut, Anda cukup memberikan NULLfungsi itu, asalkan mengembalikan non NULLpointer.

The strtokFungsi mencatat string Anda pertama kali diberikan saat Anda menyebutnya. (Yang sangat berbahaya untuk aplikasi multi-utas)

tibur
sumber
8

strtok akan memberi tokenize string yaitu mengubahnya menjadi serangkaian substring.

Itu dilakukan dengan mencari pembatas yang memisahkan token (atau substring) ini. Dan Anda menentukan pembatas. Dalam kasus Anda, Anda menginginkan '' atau ',' atau '.' atau '-' sebagai pembatas.

Model pemrograman untuk mengekstrak token ini adalah Anda menggunakan string utama dan set pembatas. Kemudian Anda memanggilnya berulang kali, dan setiap kali strtok akan mengembalikan token berikutnya yang ditemukannya. Sampai mencapai akhir string utama, ketika mengembalikan null. Aturan lainnya adalah Anda melewatkan string hanya untuk pertama kalinya, dan NULL untuk waktu berikutnya. Ini adalah cara untuk memberi tahu strtok jika Anda memulai sesi baru tokenizing dengan string baru, atau Anda mengambil token dari sesi tokenizing sebelumnya. Perhatikan bahwa strtok mengingat statusnya untuk sesi tokenisasi. Dan karena alasan ini, ini bukan reentrant atau thread safe (Anda harus menggunakan strtok_r sebagai gantinya). Hal lain yang perlu diketahui adalah itu benar-benar memodifikasi string asli. Ia menulis '\ 0' untuk pembatas yang ditemukannya.

Salah satu cara untuk memanggil strtok secara ringkas adalah sebagai berikut:

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

Hasil:

this
is
the
string
I
want
to
parse
Ziffusion
sumber
5

strtok memodifikasi string inputnya. Ini menempatkan karakter nol ('\ 0') di dalamnya sehingga akan mengembalikan bit dari string asli sebagai token. Faktanya strtok tidak mengalokasikan memori. Anda dapat memahaminya dengan lebih baik jika Anda menggambar tali sebagai urutan kotak.

xpmatteo.dll
sumber
3

Untuk memahami cara strtok()kerjanya, pertama-tama perlu diketahui apa itu variabel statis . Tautan ini menjelaskannya dengan cukup baik ....

Kunci pengoperasiannya strtok()adalah mempertahankan lokasi pemisah terakhir antara panggilan seccessive (itulah sebabnya strtok()terus mengurai string asli yang diteruskan ke sana ketika dipanggil dengan null pointerpanggilan berturut-turut) ..

Lihat strtok()implementasi saya sendiri , yang disebut zStrtok(), yang memiliki fungsi agak berbeda dari yang disediakan olehstrtok()

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

Dan berikut adalah contoh penggunaan

  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)

Kode ini berasal dari pustaka pemrosesan string yang saya simpan di Github , yang disebut zString. Lihat kodenya, atau bahkan berkontribusi :) https://github.com/fnoyanisi/zString

fnisi
sumber
3

Ini adalah bagaimana saya mengimplementasikan strtok, Tidak terlalu bagus tetapi setelah bekerja 2 jam akhirnya berhasil. Ini mendukung banyak pembatas.

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

Berikut adalah implementasi saya yang menggunakan tabel hash untuk pembatas, yang berarti O (n), bukan O (n ^ 2) (berikut adalah tautan ke kode) :

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

strtok () menyimpan pointer dalam variabel statis di mana Anda terakhir kali tinggalkan, jadi pada panggilan ke-2, ketika kita meneruskan null, strtok () mendapatkan pointer dari variabel statis.

Jika Anda memberikan nama string yang sama, itu dimulai lagi dari awal.

Terlebih lagi strtok () bersifat merusak yaitu membuat perubahan pada string orignal. jadi pastikan Anda selalu memiliki salinan orignal.

Satu lagi masalah menggunakan strtok () adalah karena ia menyimpan alamat dalam variabel statis, dalam pemrograman multithread, memanggil strtok () lebih dari sekali akan menyebabkan kesalahan. Untuk penggunaan ini strtok_r ().

Vaibhav
sumber
0

Bagi mereka yang masih kesulitan memahami strtok()fungsi ini , lihat contoh pythontutor ini , ini adalah alat yang hebat untuk memvisualisasikan kode C (atau C ++, Python ...) Anda.

Jika tautan rusak, tempelkan:

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

Penghargaan diberikan kepada Anders K.


sumber
0

Anda dapat memindai array karakter untuk mencari token jika Anda menemukannya hanya mencetak baris baru, jika tidak, cetak karakter tersebut.

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

Jadi, ini adalah cuplikan kode untuk membantu lebih memahami topik ini.

Mencetak Token

Tugas: Diberikan sebuah kalimat, s, cetak setiap kata dari kalimat tersebut di baris baru.

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

Memasukkan: How is that

Hasil:

How
is
that

Penjelasan: Jadi di sini, fungsi "strtok ()" digunakan dan diiterasi menggunakan for loop untuk mencetak token dalam baris terpisah.

Fungsi tersebut akan mengambil parameter sebagai 'string' dan 'break-point' dan memutuskan string pada break-point tersebut dan membentuk token. Sekarang, token tersebut disimpan dalam 'p' dan digunakan lebih lanjut untuk pencetakan.

tr_abhishek
sumber
Saya pikir menjelaskan melalui contoh jauh lebih baik daripada merujuk ke beberapa dokumen.
tr_abhishek