文字列の分割

strtok関数

strtok関数は、文字列を指定の区切り文字で分割します。

このページの関数を使用するには#include <string.h>が必要です。


#include <stdio.h>
#include <string.h>

int main()
{
	char str[] = "This_is_a_pen.";

	//区切りに使用する文字列
	const char* separator = "_";

	char* token = strtok(str, separator);
	while (token != NULL) {
		printf("%s\n", token);

		//二回目以降は第一引数をNULLにする
		token = strtok(NULL, separator);
	}

	printf("\n元の文字配列の中身\n");

	size_t strSize = sizeof(str);
	for (size_t i = 0; i < strSize - 1; i++) {
		//NULL文字は「/」記号に置き換えて表示
		if(str[i] == '\0')
			printf("%c", '/');
		else
			printf("%c", str[i]);
	}

	getchar();
}
This
is
a
pen.

元の文字配列の中身
This/is/a/pen.
char* strtok(
 char* str,
 const char *delimiter,
);
文字列strを、文字列delimiterのいずれかの文字で分割する。
戻り値は区切られた文字の先頭を指すポインタ。
全ての検索が終わるとNULLが返される。

この関数は使い方がやや独特なので詳しく説明します。

第一引数の文字列は書き換えられる

strtok関数での「分割」とは、区切り文字をNULL文字に置き換える処理をいいます。
つまり第一引数の文字列は関数内で書き換えられます。
そのため、第一引数には文字列リテラルを指定することはできません。
(文字列リテラルの書き換えは未定義の動作)


char* token;

//文字列リテラルの書き換えはNG
token = strtok("ABC/DEF", "/");

//これもNG
char* str1 = "ABC/DEF";	
token = strtok(str1, "/");

//配列の書き換えはOK
char str2[] = "ABC/DEF";
token = strtok(str2, "/");

//malloc等で確保したメモリ領域もOK
char* str3 = malloc(sizeof(char) * strlen(str1) + 1);
strncpy(str3, str1, strlen(str1) + 1);
token = strtok(str3, "/");

第二引数は文字の組み合わせ

第二引数は区切り文字に使用する文字列ですが、これは「文字列中のいずれかの文字」を区切り文字とみなします。
つまり文字列というよりは「文字の組み合わせ」として使用されます。
文字列内の順序が違っても動作は変わりません。


//「,」「.」「 」のいずれかの文字
token = strtok(str, ",. ");

//↑と全く同じ動作
token = strtok(str, ". ,");

あくまでも指定できるのは「文字列」ですので、区切り文字が一つだけの場合でも「文字」ではなく「文字列」として指定します。
(ダブルクォーテーションを使用する)


//NG
token = strtok(str, ',');

//OK
token = strtok(str, ",");

処理終了まで何度も呼び出す必要がある

strtok関数は一度の呼び出しでひとつのみ、NULL文字への置き換えを行います。
文字列中に区切り文字が複数ある場合、strtok関数を何度も呼び出す必要があります。
このとき、二回目以降は第一引数にNULLを指定して呼び出す必要があります。


char* token = strtok(str, separator);
while (token != NULL) {
	token = strtok(NULL, separator);
}

第二引数にNULLを指定すると、前回処理した文字列の、前回の区切り位置から処理が再開されます。
そして次の区切り文字を検索し、置き換えます。
区切り文字が見つからなくなるとNULLを返しますので、処理を終了させます。

なお、二回目以降の呼び出しでは第二引数に別の区切り文字列を指定することも可能です。

strtok_s関数、strtok_r関数

strtok関数は、二回目以降の呼び出し時に使用するために、書き換えた区切り文字の位置を内部に記憶しています。
これはマルチスレッドプログラムで使用すると問題が発生する可能性があります。
Visual Studio標準の設定では使用するとエラーになります。
(→_s系関数とエラー表示について)

この問題に対応するため、Visual Studioではstrtok_s関数、その他のコンパイラではstrtok_r関数が用意されています。
(strtok_r関数はコンパイラによっては使用できません)

これらの関数の使い方は同じです。


#include <stdio.h>
#include <string.h>

int main()
{
	char str[] = "This is a pen.";
	const char* separator = " .,";
	char* last = NULL; //前回の位置を保存するポインタ

	char* token = strtok_s(str, separator, &last);
	while (token != NULL) {
		printf("%s\n", token);

		token = strtok_s(NULL, separator, &last);
	}

	//Visual Studio以外では
	//strtok_r関数を使用する
	//char* token = strtok_r(str, separator, &last);
	//while (token != NULL) {
	//	printf("%s\n", token);

	//	token = strtok_r(NULL, separator, &last);
	//}

	printf("\n元の文字配列の中身\n");

	size_t strSize = sizeof(str);
	for (size_t i = 0; i < strSize - 1; i++) {
		//NULL文字は「/」記号に置き換えて表示
		if (str[i] == '\0')
			printf("%c", '/');
		else
			printf("%c", str[i]);
	}
	getchar();
}
This
is
a
pen

元の文字配列の中身
This/is/a/pen/
char* strtok_s(
 char* str,
 const char *delimiter,
 char** last
);
文字列strを、文字列delimiterのいずれかの文字で分割し、その位置をポインタlastに格納する。
戻り値は区切られた文字の先頭を指すポインタ。
全ての検索が終わるとNULLが返される。

strtok_r関数のほうの定義も同じなので省略します。

strtok関数では内部的に保存されていた前回の書き換え位置を、第三引数のポインタ変数で外部に保存するようになっています。
これにより状態をプログラマが管理できます。

第三引数はポインタ変数のアドレスを指定するため、ポインタ変数にアドレス演算子(&記号)を付けて指定することに注意してください。

strtok関数では二つ以上の文字列の分割を並行して行うことができませんでしたが、strtok_s関数、strtok_r関数ではそれが可能です。


#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "This is a pen.";
	char str2[] = "I am a student boy.";

	const char* separator1 = " .,";
	const char* separator2 = " .,";

	char* last1 = NULL;
	char* last2 = NULL;

	char* token1 = strtok_s(str1, separator1, &last1);
	char* token2 = strtok_s(str2, separator2, &last2);

	char finish = 0;
	while (!finish) {
		finish = 1;

		if (token1 != NULL) {
			printf("%s\n", token1);
			token1 = strtok_s(NULL, separator1, &last1);
			finish = 0;
		}
		if (token2 != NULL) {
			//タブ文字で少し右に寄せて表示
			printf("\t%s\n", token2);
			token2 = strtok_s(NULL, separator2, &last2);
			finish = 0;
		}
	}

	getchar();
}
This
        I
is
        am
a
        a
pen
        student
        boy

_mbstok関数、_mbstok_s関数

_mbstok関数、_mbstok_s関数はstrtok関数、strtok_s関数のマルチバイト文字対応版です。
文字列に日本語などのマルチバイト文字を使用できる以外は同じ動作をします。
ただしこれらの関数はVisual Studioでのみ使用できます。
(他にも対応しているコンパイラがあるかもしれないが未確認)


#include <stdio.h>
#include <string.h>
#include <locale.h>   //setlocale関数に必要
#include <mbstring.h> //必要

int main()
{
	setlocale(LC_ALL, "");

	char str[] = "あいう分えお割かきく";
	const char* separator = "分割";

	char* token = _mbstok(str, separator);
	while (token != NULL) {
		printf("%s\n", token);
		token = _mbstok(NULL, separator);
	}

	printf("\n元の文字配列の中身\n");

	size_t strSize = sizeof(str);
	for (size_t i = 0; i < strSize - 1; i++) {
		//NULL文字は「/」記号に置き換えて表示
		if (str[i] == '\0')
			printf("%c", '/');
		else
			printf("%c", str[i]);
	}

	getchar();
}
あいう
えお
かきく

元の文字配列の中身
あいう//えお//かきく