strtok()はどのように文字列をCのトークンに分割しますか?


114

strtok()機能の働きを説明してください。マニュアルには、文字列をトークンに分解すると書かれています。マニュアルでは実際に何が行われているのか理解できません。

私は上の時計を追加strし、*pch最初のwhileループが発生したとき、内容はその作業をチェックするstrだけで「これ」でした。以下に示す出力はどのように画面に印刷されましたか?

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

出力:

分割文字列「-これはサンプル文字列」。トークンに:
この
a
サンプル
ストリング

5
strtok()戻る前にNULでトークンを終了することにより、引数文字列を変更します。バッファ全体(str [])を調べようとすると、への連続した呼び出しの間に変更されているのがわかりますstrtok()
Michael Foukarakis

代わりに見てのstr、時計str[0]str[1]str[2]、...
PMGの

@pmg:str [0]とstr [1] .str [1]は '\ 0'であるはずですが、そこにはスペースがありました。
-fuddin

3
正直に言って、気にしたことはありませんが、最後に渡されたポインタと、中断された位置が格納されていると思います。その後、ポインタがNULLの場合はそのまま続行できます。そうでない場合は、位置をクリアしてやり直します。
クリス

7
@Firegun:静的変数
DCoder

回答:


38

strtok()文字列をトークンに分割します。つまり、区切り文字のいずれかから次の区切り文字まで開始すると、1つのトークンになります。あなたの場合、開始トークンは「-」から始まり、次のスペース「」で終わります。次に、次のトークンは「」で始まり、「、」で終わります。ここでは、出力として「これ」を取得します。同様に、残りの文字列はスペースからスペースにトークンに分割され、最後に「。」で最後のトークンが終了します。


1つのトークンの終了条件が次のトークンの開始トークンになりますか?終了条件の場所にヌル文字が配置されていますか?
fuddin

1
@ fahad-はい、他の人も提案したように、あなたが持っているすべての区切り文字はNUL文字に置き換えられます。
Sachin Shanbhag

すべての区切り文字がNulで置き換えられた場合、なぜ文字列に「-this」が含まれるのですか?「\ 0」が含まれている必要があります
10:12にfuddin

2
@fahad-区切り文字の間のすべての文字ではなく、区切り文字をNULに置き換えるだけです。文字列を複数のトークンに分割するようなものです。「-this」ではなく、2つの指定された区切り文字の間にある「This」を取得します。
Sachin Shanbhag

1
@ファハド-はい、絶対に。すべてのスペース、「、」、「-」は、私が理解している限り、区切り文字として指定したため、NULに置き換えられます。
Sachin Shanbhag

212

strtokランタイム関数は次のように機能します

strtokを初めて呼び出すときに、トークン化する文字列を指定します

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

上記の文字列では、スペースが単語間の区切り文字として適切であると思われるため、次のように使用します。

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

ここで何が起きるかというと、スペース文字が見つかるまで「s」が検索され、最初のトークンが返され(「this」)、pはそのトークンを指します(文字列)

strtokは以前に渡された文字列への静的ポインタを保持しているため、次のトークンを取得して同じ文字列で続行するには、最初の引数としてNULLが渡されます。

p = strtok(NULL," ");

pは「is」を指すようになりました

以降、スペースが見つからなくなるまで、最後の文字列が最後のトークン「文字列」として返されます。

より便利には、すべてのトークンを出力する代わりに、次のように書くことができます。

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

編集:

strtokたとえばstrdup(p);、元の文字列(内の静的ポインタによってポイントされているstrtok)が反復間で変更されてトークンを返すため、トークンを別のバッファにコピーする必要があるため、戻り値を格納する場合。


文字列の間にヌル文字を実際に配置しないのですか?なぜ私の時計は文字列が「これ」のみで残っていることを示しますか?
fuddin

4
実際、見つかった ''を '\ 0'に置き換えます。そして、それは後で ''を復元しないので、文字列は完全に破壊されます。

33
静的バッファの+1、これは私が理解できなかったものです
IEatBagels

1
「最初のトークンが返さpれ、そのトークンを指している」という行に欠けている非常に重要な詳細strtokは、区切り文字の代わりにnull文字を配置して元の文字列を変更する必要があることです。トークンは終了します)。また、静的変数を使用して状態を追跡します。
Groo

@Groo 2017年に行ったEditですでに追加したと思いますが、あなたは正しいです。
AndersK

25

strtok文字列内で次に使用可能なトークンを指す静的な内部参照を維持します。NULLポインターを渡すと、その内部参照から機能します。

これがstrtokリエントラントではない理由です。新しいポインターを渡すとすぐに、その古い内部参照が破壊されます。


古い内部参照の「壊れる」とはどういう意味ですか。「上書き」という意味ですか?
ylun.ca 2015年

1
@ ylun.ca:はい、それが私の言いたいことです。
John Bode、2015年

10

strtokパラメータ自体は変更しません(str)。そのポインタを(ローカル静的変数に)格納します。その後、パラメーターを戻さなくても、その後の呼び出しでそのパラメーターが指すものを変更できます。(そして、保持しているそのポインタを進めることができますが、操作を実行する必要があります。)

POSIX strtokページから:

この関数は、静的ストレージを使用して、呼び出し間の現在の文字列位置を追跡します。

strtok_rこのタイプの魔法を行わないスレッドセーフなバリアント()があります。


2
さて、Cライブラリ関数は昔からさかのぼります。スレッド化はまったく問題ではありませんでした(C標準に関する限り、2011年に存在し始めただけです)。したがって、再入可能性はそれほど重要ではありませんでした(私は推測する)。その静的ローカルは、関数を「使いやすい」にします(「簡単」の定義については)。ctime静的な文字列を返すように-実用的です(誰がそれを解放する必要があるのか​​誰も気にする必要はありません)。
マット

これは間違っています:「strtokパラメーター自体は変更されません(str)。」変更puts(str);後、「-This」を出力します。strtokstr
MarredCheese

1
@MarredCheese:もう一度お読みください。ポインターは変更されません。ポインターが指すデータ(文字列データなど)を変更します
Mat

ああ、そうだとは思わなかった。同意した。
MarredCheese

8

初めて呼び出すときに、トークン化する文字列を提供しますstrtok。そして、次のトークンを取得するNULLには、非NULLポインターを返す限り、その関数に与えるだけです。

このstrtok関数は、呼び出し時に最初に指定した文字列を記録します。(これはマルチスレッドアプリケーションにとって本当に危険です)


8

strtokは文字列をトークン化します。つまり、一連の部分文字列に変換します。

これは、これらのトークン(またはサブストリング)を区切る区切り文字を検索することによって行われます。そして、区切り文字を指定します。あなたのケースでは、「」または「、」または「。」が必要です。または区切り文字となる「-」。

これらのトークンを抽出するプログラミングモデルは、メイン文字列と区切り文字のセットをstrtokで渡すことです。次に、それを繰り返し呼び出して、strtokが見つけるたびに次のトークンを返します。メイン文字列の最後に到達するまで、nullを返します。もう1つのルールは、文字列を最初にのみ渡し、それ以降はNULLを渡すことです。これは、新しい文字列を使用してトークン化の新しいセッションを開始する場合、または以前のトークン化セッションからトークンを取得する場合にstrtokに通知する方法です。strtokはトークン化セッションの状態を記憶していることに注意してください。このため、再入可能でもスレッドセーフでもありません(代わりにstrtok_rを使用する必要があります)。もう1つ知っておくべきことは、実際に元の文字列を変更することです。検出した区切り文字に「\ 0」を書き込みます。

strtokを簡潔に呼び出す1つの方法は次のとおりです。

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

結果:

this
is
the
string
I
want
to
parse

5

strtokは入力文字列を変更します。ヌル文字( '\ 0')を配置して、元の文字列のビットをトークンとして返します。実際、strtokはメモリを割り当てません。文字列を一連のボックスとして描画すると、理解しやすくなります。


3

どのようにstrtok()機能するかを理解するには、最初に静的変数が何であるかを知る必要がありますこのリンクはそれをかなりよく説明します...

の操作の鍵は、strtok()連続する呼び出しの間の最後のセパレーターの位置を維持することです(そのstrtok()ため、で呼び出されたときに渡された非常に元の文字列を引き続き解析しますnull pointer、連続した呼び出しでし続けます)。

strtok()と呼ばれる私の独自の実装を見てください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;
}

そしてここに使用例があります

  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)

コードは、zStringと呼ばれる、Github維持している文字列処理ライブラリからのものです。コードを見て、または貢献してください :) https://github.com/fnoyanisi/zString


3

これは、私がstrtokを実装した方法です。それほど大きくはありませんが、2時間作業した後、ようやく機能しました。複数の区切り文字をサポートしています。

#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

これが区切り文字にハッシュテーブルを使用する私の実装です。つまり、O(n ^ 2)ではなくO(n)です(ここにコードへのリンクがあります)

#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()は、最後に中断した静的変数にポインターを格納するため、2回目の呼び出しでnullを渡すと、strtok()は静的変数からポインターを取得します。

同じ文字列名を指定すると、再び最初から始まります。

さらに、strtok()は破壊的です。つまり、元の文字列を変更します。常にオリジナルのコピーを用意してください。

strtok()を使用する際のもう1つの問題は、アドレスを静的変数に格納するため、マルチスレッドプログラミングでstrtok()を複数回呼び出すとエラーが発生することです。これにはstrtok_r()を使用します。


0

まだこのstrtok()機能を理解するのに苦労している人のために、このpythontutorの例を見てください。これは、C(またはC ++、Python ...)コードを視覚化するための優れたツールです。

リンクが壊れた場合は、次の場所に貼り付けてください。

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

クレジットはAnders K送られます。


0

char配列をスキャンしてトークンを探し、それが新しい行を出力するだけの場合は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;
}

0

したがって、これはこのトピックをよりよく理解するのに役立つコードスニペットです。

トークンの印刷

タスク:文sを指定して、文の各単語を新しい行に出力します。

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

入力: How is that

結果:

How
is
that

説明:したがって、ここでは「strtok()」関数が使用され、トークンを別々の行に出力するためにforループを使用して反復されています。

この関数は、パラメータを「文字列」と「ブレークポイント」として受け取り、これらのブレークポイントで文字列を分割し、トークンを形成します。現在、これらのトークンは「p」に格納され、さらに印刷に使用されます。


いくつかのドキュメントを参照するよりも、例を使用して説明する方がはるかに良いと思います。
tr_abhishek
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.