Strtok () dizeyi C'deki belirteçlere nasıl böler?


114

Lütfen bana strtok()fonksiyonun çalışmasını açıklayın . Kılavuz, dizeyi jetonlara böldüğünü söylüyor. Kılavuzdan gerçekte ne yaptığını anlayamıyorum.

Saatler ekledim strve *pchilk döngü oluştuğunda çalışıp çalışmadığını kontrol etmek için içeriği strsadece "bu" idi. Aşağıda gösterilen çıktı ekrana nasıl yazdırıldı?

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

Çıktı:

Splitting string "- Bu, örnek bir dize." belirteçlere:
Bu
bir
örneklem
sicim

5
strtok()dönmeden önce belirteçleri NUL ile sonlandırarak argüman dizesini değiştirir. Tüm tamponu (str []) incelemeye çalışırsanız, ardı ardına yapılan aramalar arasında değiştirildiğini görürsünüz strtok().
Michael Foukarakis

Yerine izlerken str, saat str[0], str[1], str[2], ...
PMG

@pmg: str [0] ve str [1] .str [1] 'nin' \ 0 'olması gerektiğini izledim ama orada bir boşluk vardı.
fuddin

3
Dürüst olmak gerekirse kontrol etme zahmetine girmedim, ama geçen son işaretçiyi bıraktığı konumla birlikte sakladığını hayal ediyorum. Ardından, işaretçi BOŞ ise devam edebilir veya konumu temizleyip, değilse baştan başlayabilir.
chris

7
@Firegun: statik değişken .
DCoder

Yanıtlar:


38

strtok()dizeyi belirteçlere böler. yani, herhangi bir sınırlayıcıdan bir sonrakine başlamak sizin tek jetonunuz olacaktır. Sizin durumunuzda, başlangıç ​​jetonu "-" arasında olacak ve sonraki boşluk "" ile bitecektir. Ardından, sonraki simge "" ile başlayacak ve "," ile bitecektir. Burada çıktı olarak "Bu" elde edersiniz. Benzer şekilde, dizenin geri kalanı uzaydan boşluğa jetonlara bölünür ve sonunda son jeton "" üzerinde sona erer.


Bir jetonun bitiş koşulu, bir sonraki jetonun başlangıç ​​jetonu olur - ayrıca bitiş koşulunun yerine yerleştirilmiş bir boş karakter var mı?
fuddin

1
@ fahad- Evet, sahip olduğunuz tüm sınırlayıcılar, diğer kişilerin de önerdiği gibi NUL karakteriyle değiştirilecektir.
Sachin Shanbhag

Tüm sınırlayıcılar Nul ile değiştirilirse, dize neden "-bu" içeriyor? "\ 0" içermelidir
fuddin

2
@fahad - Yalnızca sınırlayıcı karakterleri NUL ile değiştirir, sınırlayıcılar arasındaki tüm karakterleri değil. Dizeyi birden çok simgeye bölme türüdür. Bu "-bu" değil, belirtilen iki sınırlayıcı arasında olduğu için "Bu" alırsınız.
Sachin Shanbhag

1
@Fahad - Evet, kesinlikle. Anladığım kadarıyla, tüm boşluklar, "," ve "-" NUL ile değiştirildi çünkü bunları sınırlayıcı olarak belirttiniz.
Sachin Shanbhag

214

strtok çalışma zamanı işlevi şu şekilde çalışır

strtok'u ilk aradığınızda, belirtmek istediğiniz dizeyi sağlarsınız

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

Yukarıdaki dizede boşluk, kelimeler arasında iyi bir sınırlayıcı gibi görünüyor, bu yüzden şunu kullanalım:

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

şimdi olan şey, boşluk karakteri bulunana kadar 's' aranır, ilk simge döndürülür ('bu') ve p bu simgeyi (dizeyi) gösterir

sonraki belirteci almak ve aynı dizeyle devam etmek için, strtok önceki geçirilen dizeniz için statik bir işaretçi tuttuğundan, ilk argüman olarak NULL geçirilir:

p = strtok(NULL," ");

p şimdi 'eşittir'i gösterir

ve bu şekilde, daha fazla boşluk bulunmayana kadar, son dize, son simge 'dizge' olarak döndürülür.

daha rahat bir şekilde, tüm belirteçleri yazdırmak yerine şu şekilde yazabilirsiniz:

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

DÜZENLE:

Döndürülen değerleri saklamak istiyorsanız strtok, belirteci başka bir tampona kopyalamanız gerekir, örneğin strdup(p);, orijinal dizge (içerideki statik işaretçi tarafından işaret edilir strtok), simgeyi geri döndürmek için yinelemeler arasında değiştirildiği için.


Yani aslında dizge arasına boş bir karakter koymuyor? Saatim neden dizenin yalnızca "BU" ile kaldığını gösteriyor?
43'te fuddin

4
gerçekten de bulduğu '' yerine '\ 0' koyar. Ve daha sonra geri yüklenmez '', bu yüzden dizeniz tamamen mahvolur.

33
Statik arabellek için +1, anlamadığım şey bu
IEatBagels

1
Satırından eksik çok önemli bir ayrıntı, "ilk belirteci döndürülür ve ppuanları bu simge için" , yani strtoksınırlayıcı yerine boş karakter koyarak orijinal dize mutasyona ihtiyaçlar (Aksi diğer dize işlevleri nerede bilemeyiz jeton biter). Ayrıca statik bir değişken kullanarak durumu izler.
Groo

@Groo 2017'de yaptığım Edit'de bunu zaten ekledim galiba ama haklısın.
AndersK

25

strtokdizede bir sonraki kullanılabilir belirteci işaret eden statik, dahili bir referans tutar; Eğer ona bir NULL gösterici geçirirseniz, bu dahili referanstan çalışacaktır.

Bunun nedeni strtokyeniden giriş değil; ona yeni bir işaretçi verir vermez, bu eski iç referans bozulur.


Eski iç referansla neyi kastediyorsunuz 'yuhalanmak'. Üzerine yazılmış mı demek istiyorsun?
ylun.ca

1
@ ylun.ca: evet, demek istediğim bu.
John Bode

10

strtokparametrenin kendisini değiştirmez ( str). Bu işaretçiyi (yerel bir statik değişkende) depolar. Daha sonra , parametre geri aktarılmadan, sonraki çağrılarda bu parametrenin işaret ettiği şeyi değiştirebilir . (Ve tuttuğu işaretçiyi, işlemlerini gerçekleştirmesi gerektiği şekilde ilerletebilir.)

POSIX strtoksayfasından:

Bu işlev, çağrılar arasındaki geçerli dizi konumunu izlemek için statik depolamayı kullanır.

strtok_rBu tür bir sihir yapmayan iş parçacığı güvenli bir varyant ( ) vardır.


2
Pekala, C kütüphanesi fonksiyonları, geçmişten beri, iş parçacığı hiç görünmediği zamana dayanıyor (bu, C standardı söz konusu olduğunda sadece 2011'de var olmaya başladı), bu yüzden yeniden giriş gerçekten önemli değildi ( Sanırım). Bu statik yerel, işlevi "kullanımı kolay" yapar ("kolay" ın bazı tanımları için). ctimeStatik bir dizeyi döndürmek gibi - pratik (kimsenin onu kimin serbest bırakması gerektiğini merak etmesi gerekmez), ancak yeniden giriş yapmaz ve çok farkında değilseniz sizi ayağa kaldırır.
Mat

Bu yanlış: " strtokparametrenin kendisini değiştirmez ( str)." değiştirildikten sonra puts(str);"- Bu" yazdırır . strtokstr
MarredCheese

1
@MarredCheese: tekrar okuyun. İşaretçiyi değiştirmez. İmlecin işaret ettiği verileri değiştirir (yani dizi verisi)
Mat

Oh tamam, neye yaklaştığının farkında değildim. Kabul.
MarredCheese

8

İlk kez aradığınızda, belirtmek için dizeyi sağlarsınız strtok. Ve sonra, aşağıdaki belirteçleri elde NULLetmek için, NULLişaretçi olmayan bir döndürdüğü sürece, o işleve vermeniz yeterlidir.

strtokİşlevi diyoruz ilk ne zaman sağlanan dize kaydeder. (Bu, çok iş parçacıklı uygulamalar için gerçekten tehlikelidir)


8

strtok bir dizgeyi belirtecek, yani onu bir dizi alt dizeye çevirecektir.

Bunu, bu simgeleri (veya alt dizeleri) ayıran sınırlayıcıları arayarak yapar. Ve sınırlayıcıları belirtirsiniz. Sizin durumunuzda, "veya", "veya" "istersiniz. veya sınırlayıcı olarak '-'.

Bu belirteçleri ayıklamak için kullanılan programlama modeli, ana dizenizi ve sınırlayıcılar kümenizi strtok haline getirmenizdir. Sonra onu tekrar tekrar çağırırsınız ve strtok bulduğu bir sonraki jetonu her döndürdüğünde. Bir boş döndürdüğünde ana dizenin sonuna ulaşıncaya kadar. Diğer bir kural, dizeyi yalnızca ilk seferde geçirmeniz ve sonraki zamanlar için NULL yapmanızdır. Bu, strtok'a yeni bir dizge ile yeni bir dizge oluşturma oturumuna başlıyorsanız veya önceki bir belirteç oluşturma oturumundan belirteçler alıyorsanız bunu söylemenin bir yoludur. Strtok'un belirteçleme oturumu için durumunu hatırladığını unutmayın. Ve bu nedenle evresel veya iş parçacığı güvenli değildir (bunun yerine strtok_r kullanmanız gerekir). Bilinmesi gereken başka bir şey de, orijinal dizeyi gerçekten değiştirdiğidir. Bulduğu sınırlayıcılar için '\ 0' yazar.

Kısa ve öz olarak strtok'u çağırmanın bir yolu şöyledir:

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

Sonuç:

this
is
the
string
I
want
to
parse

5

strtok, girdi dizesini değiştirir. İçine boş karakterler ('\ 0') yerleştirir, böylece orijinal dizenin bitlerini simge olarak döndürür. Aslında strtok bellek ayırmaz. Dizeyi bir dizi kutu olarak çizerseniz daha iyi anlayabilirsiniz.


3

Nasıl strtok()çalıştığını anlamak için önce statik değişkenin ne olduğunu bilmek gerekir . Bu bağlantı bunu oldukça iyi açıklıyor ...

Çalışması için anahtar strtok()seccessive çağrılar arasında geçen seperatörü konumunu muhafaza etmektedir (işte bu yüzden strtok()bir çalıştırıldığında kendisine iletilen çok orijinal dize ayrıştırmak devam null pointerizleyen aramalar) ..

Tarafından sağlanandan biraz farklı bir işlevselliğe sahip olan kendi strtok()uygulamama bir göz atın zStrtok().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;
}

Ve işte örnek bir kullanım

  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)

Kod, Github'da tuttuğum zString adlı bir dizi işleme kitaplığından . Kodu inceleyin, hatta katkıda bulunun :) https://github.com/fnoyanisi/zString


3

Strtok'u bu şekilde uyguladım, O kadar iyi değil ama 2 saat çalıştıktan sonra sonunda işe yaradı. Birden çok sınırlayıcıyı destekler.

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


1

İşte sınırlayıcı için karma tablo kullanan uygulamam, yani O (n ^ 2) yerine O (n) (burada koda bir bağlantı var) :

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

1

strtok (), işaretçiyi en son nerede kaldığınız statik değişkende depolar, bu nedenle 2. çağrısında, null değerini ilettiğimizde strtok (), göstericiyi statik değişkenden alır.

Aynı dize adını sağlarsanız, yeniden baştan başlar.

Ayrıca strtok () yıkıcıdır, yani orijinal dizgede değişiklikler yapar. bu yüzden her zaman orijinal bir kopyasına sahip olduğunuzdan emin olun.

Strtok () kullanmanın diğer bir problemi, adresi statik değişkenler içinde sakladığından, çok iş parçacıklı programlamada strtok () 'u birden çok kez çağırmanın bir hataya neden olmasıdır. Bunun için strtok_r () kullanın.


0

Hala bu strtok()işlevi anlamakta zorlananlar için , bu pythontutor örneğine bir bakın, C (veya C ++, Python ...) kodunuzu görselleştirmek için harika bir araçtır.

Bağlantının kopması durumunda yapıştırın:

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

Krediler Anders K.


0

Eğer onu yeni bir satır yazdırırsanız, yoksa karakteri yazdırırsanız, belirteci arayan karakter dizisini tarayabilirsiniz.

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

0

Dolayısıyla, bu konuyu daha iyi anlamanıza yardımcı olacak bir kod parçacığıdır.

Belirteçleri Yazdırma

Görev: Bir cümle verildiğinde, cümlenin her kelimesini yeni bir satıra yazdırın.

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

Giriş: How is that

Sonuç:

How
is
that

Açıklama: Burada "strtok ()" işlevi kullanılır ve jetonları ayrı satırlarda yazdırmak için for döngüsü kullanılarak yinelenir.

İşlev, parametreleri 'dizge' ve 'kesme noktası' olarak alacak ve dizeyi bu kesme noktalarında ve form belirteçlerinde kesecektir. Şimdi, bu simgeler 'p'de saklanıyor ve daha sonra yazdırma için kullanılıyor.


Bir örnekle açıklamanın bir belgeye atıfta bulunmaktan çok daha iyi olduğunu düşünüyorum.
tr_abhishek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.