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()の戻り値を直接その場でそのまま使ってしまう方が効率はいい、ということかなー…。