質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

7回答

7325閲覧

C言語 strtok関数の自作に関して

P_Beginner

総合スコア99

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

1クリップ

投稿2018/05/07 15:28

編集2018/05/08 15:36

strtok関数を自作して、「,」で区切られた文字列を第1引数にとって、関数を呼び出すたびに順番に区切られた部分を文字列として返すようなプログラムを作りたいのですが、strtok自体があまり理解できず関数の自作も難航しています。

strtok関数を自作する上でのアルゴリズムのヒントなどをご教授していただけると幸いです。

ちなみに今回の関数では、第1引数がNULLのときは前回の文字列の探索の続きを行えるように、新しく文字列が与えられたら最初から、切り出しが終了したらNULLを返せるようにする条件が付いています。
可能であればカンマ(区切り文字)が連続した際に返す文字も工夫してください(私の場合はとりあえずNULLにしますが)。

イメージ的にはこんな感じにしたいです(下)

C

1int main(void){ 2 3 char str[]="abc,xyz,012"; //strは自分でmain関数を開き,下のprintf文の関数呼び出しの引数部分に直接書き込んでも良い 4 char str_ans[]; 5 //実行結果(表示形式) 6 printf("%s¥n",myStrtok(str,str_ans)); //abc 7 printf("%s¥n",myStrtok(NULL,str_ans)); //xyz 8 printf("%s¥n",myStrtok(NULL,str_ans)); //012 9 printf("%s¥n",myStrtok(NULL,str_ans)); //NULL 10 11 return 0; 12}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答7

0

strtokのような感じとは言え、同じ処理ではないので、strtokを気にしないほうがいいかもしれません。
(知識として知っておくのは全然構いませんが)

たぶんこんな感じの流れで書けばよいと思います。

  1. 引数1がNULLではなかったら、ローカルstatic変数に値を保持する。
  2. ローカルstatic変数で参照できる実体の値を1バイト、引数2に代入する。
  3. ローカルstatic変数と引数2の値(アドレス)をインクリメント。
  4. 2~3をカンマまたはNULL文字が見つかるまで繰り返す。
  5. 引数2にNULLを代入する。

投稿2018/05/08 02:54

ttyp03

総合スコア16998

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

状態を覚えておかなくてはならないためにstatic変数を使うと、複数の場所で同時に使うような用途では使えなくなります
なので、その状態変数(おそらくchar*)を外部から与えるようにしなければならないってことになりますが、これも分かりづらく、使いづらいものになってしまいます。そこをどうするかですね

投稿2018/05/07 22:27

y_waiwai

総合スコア87774

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

  • 第1引数がNULLの時に前回の続きを返すためには、前回どこまでチェックしたかを関数内で覚えておく必要があります。これはstatic変数を使うことになるかと思います。
  • strtokは呼び出される度に区切り文字の位置を探して、見つけた場所を¥0に変えていきます。strlenなどと異なり第1引数にconstが付いていないのですが、それはこのような書き換えを関数内で行うためです。

ヒントになりますでしょうか。

(追記)
すいません、変数名からすると、標準のstrtokとは異なり、第2引数で指定したバッファに結果を格納したいということのようですね。2番目のヒントは標準のstrtokを実現する場合のものですので無視してください。

投稿2018/05/07 16:07

編集2018/05/07 16:25
segavvy

総合スコア958

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

C

1char* myStrtok(char* str, char* pdst) 2{ 3 static char* p = NULL; 4 5 for(str || p = str; ',' != *p; p++, pdst++){ 6 *pdst = *p; 7 } 8 return ++p; 9}

指定の仕様なら自分なら↑ですかね?

もっとも、私なら以下の要件を削除し、↓のソースコードを書きます。いずれも動作未確認です。

第1引数がNULLのときは前回の文字列の探索の続きを行えるように、新しく文字列が与えられたら最初から、切り出しが終了したらNULLを返せるようにする条件が付いています。

C

1char* myStrtok(char* p, char* pdst) 2{ 3 for(; ',' != *p; p++, pdst++){ 4 *pdst = *p; 5 } 6 return ++p; 7} 8 9 10//呼び出す場所 11char str[]="abc,xyz,012"; 12char str_ans[]; 13char* p = str; 14 15p = myStrtok(p,str_ans); //abc 16p = myStrtok(p,str_ans); //xyz 17p = myStrtok(p,str_ans); //012 18p = myStrtok(p,str_ans); //NULL 19 20

投稿2018/05/08 13:30

HogeAnimalLover

総合スコア4830

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

題意にはそぐわないと思いますが・・・ヒントになれば
また、終端('\0')が見つかっても再度separate()を呼び出します・・・改良の余地あり・・・

c

1usr~/test % ./a.out 2abc 3xyz 4 5 6012 7usr~/test % cat separate.c 8#include <stdio.h> 9// 10const char *separate(const char *str, const char delimiter, char * destination) 11{ 12 if( *str == '\0' ){ 13 return NULL; 14 } 15 while( *str != delimiter && *str != '\0' ){ 16 *destination++ = *str++; 17 } 18 *destination= '\0'; 19 20 return *str != '\0' ? ++str: str; 21} 22 23int main() 24{ 25 char str[]="abc,xyz,,,012"; 26 char buf[10]; 27 const char *pos= str; 28 // 29 while((pos= separate(pos, ',', buf)) != NULL){ 30 puts(buf); 31 } 32 // 33 return 0; 34} 35usr~/test %

投稿2018/05/08 12:25

編集2018/05/08 13:12
cateye

総合スコア6851

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

strtokと等価な動作は、おおむね以下のような処理で実現できます。
strtok関数のプロトタイプ宣言がchar *strtok(char *str, const char *delim)だとして、流れを説明してみます。
※オリジナルのstrtokでは区切り文字を複数指定できますが、下の例では1文字に限定していますので注意してください。


  1. 引数(char *str)で指定された文字列先頭アドレスを返り値にセットする。(後で使います)

  引数がNULLであれば、保存済みのstatic変数NEXTの値を先頭アドレスとして使う。

  1. 先頭アドレスから文字列末端'\0'(NULL文字)まで検索し、指定の区切り文字(const char *delim)を見つける

  2. 区切り文字が無く、末端'\0'(NULL文字)まで行ったら次回に出力するトークン文字列は無いということなので、static変数NEXTにNULLをセットし、5.へ。

  3. 区切り文字があれば、区切り文字の位置に'\0'(NULL文字)を埋め込む。'\0'(NULL文字)を埋め込んだ次の位置をstatic変数NEXTへ保存しておく。(次にstrtokが呼ばれた時に使うことを想定)

  4. 1で保存していた文字列先頭アドレスを返す。


注意など

オリジナルのstrtokは内部に次のトークン文字列へのポインタ(上記のNEXT変数)を保存するので、リエントラント(再入可能)ではなく、マルチスレッドでのプログラミングで使えないという制限があります。

その制限をなくした、static変数で内部に保存しないバージョンのstrtok_rと言う関数もあります。
Man page of STRTOK

strtokstrtok_rも、ひとつ落とし穴(?)があって、区切り文字が連続している場合に空文字列がトークン文字列として返されません。その為、CSV形式のようなもので、文字列長0のデータがあるようなケースでは期待したように動作しません。以下の例をご覧ください。

C

1#include <stdio.h> 2#include <string.h> 3 4int main(void) { 5 const char orig[] = "item1, item2,,,,item4, item5"; 6 const char delims[] = ","; 7 char sz[256]; 8 9 strcpy(sz, orig); 10 11 char *pref = strtok(sz, delims); 12 int i = 1; 13 while (pref != NULL) { 14 printf("%d: [%s]\n", i++, pref); 15 pref = strtok(NULL, delims); 16 } 17 18 return 0; 19}

これを実行すると以下のようになります。

$ ./a.exe 1: [item1] 2: [ item2] 3: [item4] 4: [ item5]

CSVであれば、これはちょっと期待していない動作だと思いますので、自作するときはこれの扱いをどうするか、がひとつの検討課題になると思います。(私はこの挙動のせいで、過去、何度も自作しましたw)

投稿2018/05/08 02:29

編集2018/05/08 02:36
dodox86

総合スコア9183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

もう、邪道ですが、一応方法として。

他の方々も仰っているように、「staticな変数で保存している」と思います。

あくまでイメージ。

C

1char* Strtok( char *str1, const char *str2 ){ 2 static char temp[100]; // <- これ! 3 if( str1 != NULL ) strcpy( temp, str1 ); // データがNULLなら前回までのデータで 4 ... // 処理... 5}

のようになっていると思います。

でも、これだとデータを上書きしてしまう可能性があるので、C++ありなら、

私なら クラス化します。

"C言語で" と言っているのにC++って...ってことで邪道ですが。( そもそも条件すら満たしていない... )

投稿2018/05/08 01:35

BeatStar

総合スコア4958

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問