strtok()は、C言語で 1つの文字列を指定した区切り文字(例えばカンマとか)で分割する関数。
(strtokの関数名は、たぶんstring token)
元となる文字列が変更されるので、非推奨な関数ではあるが…他にいいのが無いからしょうがない。
Javaのsplitみたいに配列にして返してくれれば便利なんだけど、C言語ではメモリの扱いをきちんとしなければならないので、なかなか面倒。
そこで、strtok()を使って単語に分割し、配列にして返す例を作ってみた。
#include <string.h>
/* 指定された区切り文字で分割した文字列の配列を返す。配列の最後尾はNULLになっている。 エラーの場合はNULLを返す。 使用後はsplit_free()を呼び出してメモリを解放する必要がある。 strtok()を使っているので、MT非セーフ。 */ char** split(const char *str, const char *delim) { char **arr = NULL; //配列 int n = 0; //配列の個数 char *buf, *tp; if (str == NULL || delim == NULL) { return NULL; } buf = (char*)malloc(strlen(str) + 1); if (buf == NULL) { return NULL; } strcpy(buf, str); for(tp = strtok(buf, delim); tp; tp = strtok(NULL, delim)) { char **a = (char**)realloc(arr, sizeof(*arr) * (n+3)); if (a == NULL) { free(arr); free(buf); return NULL; } a[++n] = tp; arr = a; } if (arr != NULL) { arr[0] = buf; //元のバッファを保持する arr++; arr[n] = NULL; //終端 } return arr; } /* split()で確保したメモリを解放する。 */ void split_free(char **arr) { if (arr != NULL) { arr--; free(arr[0]); //元のバッファを解放する free(arr); } }
/*使用例*/ char **arr = split("aaa,bb,cccc", ","); // char **arr = split("aaaccc", ","); // char **arr = split(",a1,a2,a3", ","); // char **arr = split(",a1,,a3,,", ","); if (arr != NULL) { int i; for(i = 0; arr[i] != NULL; i++) { printf("%d:%s\n", i, arr[i]); } split_free(arr); }
strtok()は元の文字列の区切り文字を空文字('\0')に置き換えて、それぞれの先頭アドレスを返す。
したがって、本当の大元の文字列を書き換えないようにするためにbufを確保してコピーし、それをstrtok()の引数にしている。
また、配列はrealloc()を使って確保しているが、直接arr=realloc(arr,〜)
としちゃいけないのがまた面倒。
どうでもいいプログラムなら、メモリ確保できなかった場合の戻り値NULLなんかは無視しちゃうんだけどねー(苦笑)
戻り値は一つしか返せない(引数でポインター渡しにするのは嫌だ)ので、配列の個数を返したいところだけど、諦める。
NULLで終端とすることにする。(main()関数の第三引数のenvpと一緒)
さらに、元となるbufもどこかで保持しなければならない。strtok()が返すのはbuf内の位置だから。
(大抵の実装の場合、)普通はarr[0]がbufの先頭アドレスと等しくなるんだけど、区切り文字で始まっている文字列の場合、配列の要素に空文字列は入らないので、arr[0]とbufは一致しない。
(strtok()では、空文字列は無視される)
通常の場合の例 | カンマから始まる例 | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
str | buf | arr | str | buf | arr | |||||||
C000 |
a |
D000 |
a |
arr[0] |
D000 |
C000 |
, |
D000 |
||||
C001 |
, |
D001 |
\0 |
C001 |
a |
D001 |
a |
arr[0] |
D001 |
|||
C002 |
b |
D002 |
b |
arr[1] |
D002 |
C002 |
, |
D002 |
\0 |
|||
C003 |
\0 |
D003 |
\0 |
C003 |
b |
D003 |
b |
arr[1] |
D003 |
|||
C004 |
\0 |
D004 |
\0 |
という訳で、配列arrにさらにポインター一個分の要素を確保し、bufを保持しておくことにした。
保持する場所はNULL終端の後ろでも良かったんだけど、それだと解放時にNULLをサーチしないといけなくなるので、配列の先頭にした。なので、split()が返すポインターは
確保した領域の先頭からは1つずれている。
…要するに、strtok()を使うのであれば、strtok()の戻り値を直接その場でそのまま使ってしまう方が効率はいい、ということかなー…。