strtok()
関数の動作を説明してください。マニュアルでは、文字列をトークンに分割していると書かれています。マニュアルから実際に何をするのか理解できません。
str
と*pch
にウォッチを追加して、その動作を確認しました。最初のwhileループが発生したとき、str
の内容は「this」のみでした。以下に示す出力はどのように画面に印刷されましたか?
/* 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;
}
出力:
分割文字列「-これ、サンプル文字列」。トークンへ: This a sample string
strtok()
は、文字列をトークンに分割します。つまり、区切り文字のいずれかから次の区切り文字までが、1つのトークンになります。あなたの場合、開始トークンは「-」から始まり、次のスペース「」で終わります。次に、次のトークンは「」で始まり、「、」で終わります。ここでは、出力として「これ」を取得します。同様に、文字列の残りはスペースからスペースへのトークンに分割され、最後に「。」で最後のトークンを終了します。
strtokランタイム関数は次のように機能します
strtokを初めて呼び出すときは、トークン化する文字列を指定します
char s[] = "this is a string";
上記の文字列スペースでは、単語間の適切な区切り文字と思われるので、それを使用してみましょう。
char* p = strtok(s, " ");
今起こっていることは、スペース文字が見つかるまで「s」が検索され、最初のトークンが返され(「this」)、pがそのトークンを指すことです(文字列)
次のトークンを取得し、同じ文字列で続行するには、strtokが静的ポインタを以前に渡された文字列に維持するため、最初の引数としてNULLが渡されます。
p = strtok(NULL," ");
pは「is」を指すようになりました
以降、スペースが見つからなくなるまで、最後の文字列が最後のトークン「string」として返されます。
もっと便利に、代わりにこのように書いてすべてのトークンを印刷することができます:
for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
puts(p);
}
編集:
strtok
から返された値を保存する場合は、トークンを別のバッファーにコピーする必要があります。 strdup(p);
(元の文字列(strtok
内の静的ポインターが指す)は、トークンを返すために反復間で変更されるため。
strtok
は、文字列内で次に使用可能なトークンを指す静的な内部参照を維持します。 NULLポインターを渡すと、その内部参照から機能します。
これがstrtok
がリエントラントでない理由です。新しいポインタを渡すとすぐに、その古い内部参照が破壊されます。
strtok
は、パラメーター自体(str
)を変更しません。そのポインターを(ローカルの静的変数に)格納します。その後、パラメータを返さずに、後続の呼び出しでそのパラメータが指すものをに変更できます。 (そして、操作を実行する必要があるが、保持しているポインターを進めることができます。)
POSIX strtok
ページから:
この関数は、静的ストレージを使用して、呼び出し間の現在の文字列位置を追跡します。
この種の魔法を行わないスレッドセーフなバリアント(strtok_r
)があります。
初めて呼び出すときは、strtok
にトークン化する文字列を指定します。そして、次のトークンを取得するには、NULL
ポインターを返さない限り、その関数にNULL
を与えるだけです。
strtok
関数は、呼び出し時に最初に指定した文字列を記録します。 (これはマルチスレッドアプリケーションにとって本当に危険です)
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
strtokは入力文字列を変更します。ヌル文字( '\ 0')を配置して、元の文字列のビットをトークンとして返します。実際、strtokはメモリを割り当てません。文字列を一連のボックスとして描画すると、理解しやすくなります。
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)
コードは Githubで管理している文字列処理ライブラリ 、zStringと呼ばれます。コードをご覧になるか、貢献してください:) https://github.com/fnoyanisi/zString
strtokは、2番目の引数の文字をNULLに置き換え、NULL文字も文字列の末尾になります。
これは私が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;
}
これはデリミタにハッシュテーブルを使用する実装です。つまり、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;
}
strtok()は、前回中断した静的変数にポインターを格納するため、2回目の呼び出しでnullを渡すと、strtok()は静的変数からポインターを取得します。
同じ文字列名を指定すると、再び最初から開始されます。
さらに、strtok()は破壊的です。つまり、元の文字列に変更を加えます。常にオリジナルのコピーを持っていることを確認してください。
Strtok()を使用するもう1つの問題は、アドレスを静的変数に格納するときに、strtok()を複数回呼び出すマルチスレッドプログラミングでエラーが発生することです。これにはstrtok_r()を使用します。
まだこの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。