C言語で文字列の分割方法を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
strtok/strtok_r/strsep関数
1 2 3 |
char *strtok(char *str, const char *delim); char *strtok_r(char *str, const char *delim, char **saveptr); char *strsep(char **stringp, const char *delim); |
strtok/strtok_r/strsep関数と注意点を解説していきます.
strtok関数
strtok関数は,第1引数strの文字列を0個以上の空でないトークンに分割します.
第2引数のdelimには,解析対象の文字列をトークンに分割するために利用する文字列(分割文字の集合)を指定します.
strtok関数の最初の呼び出しでは,解析する文字列をstrで指定します.
同じ文字列を解析する2回目以降の呼び出しでは,strはNULLでなければなりません.
strtok_r関数
strtok_r関数は,strtok関数のリエントラント版です.
第3引数のsaveptrは,同じ文字列を解析する連続した呼び出しの間でコンテキストを維持するためにstrtok_r関数の内部で利用するchar *型の変数へのポインタです.
strtok_r関数の最初の呼び出しでは,解析する文字列をstrで指定し,saveptrの値は無視されます.
※実装によっては,strtok_r関数を最初に呼び出してstrを解析する際,saveptrをNULLに設定する必要があります.
同じ文字列を解析する2回目以降の呼び出しでは,strはNULLでなければならず,saveptr(およびそれが指すバッファ)は前回の呼び出しから変更されていなければなりません.
strtok_r関数はGCC/Clangでは実行できますが,C89/C99準拠ではないのでVisual Studioでは実行できないことに注意して下さい.
strsep関数
strsep関数は,strtok/strtok_r関数と同様に文字列をトークンに分割して取り出します.
strtok/strtok_r関数とは異なり,strsep関数は空のフィールドを扱うことができます.
strtok_関数と同様に,strsep関数はVisual Studioでは実行できないことに注意して下さい.
移植性を考慮する場合は,C89/C99準拠のstrtok関数の利用をおすすめします.
strtok/strtok_r/strsep関数の注意点
strtok/strtok_r/strsep関数の注意点は以下になります.
知らないとバグの原因になりますので,利用する時は注意しましょう.
- 最初の引数を変更すること(関数の呼び出し後は違う文字列に変更)
- 定数文字列には使えないこと(例:const型修飾子で宣言されている文字列,char *s型で定義された文字列)
- 区切り文字自体は失われてしまうこと('\0'文字で上書き)
strtok/strtok_r/strsep関数の使い方
strtok/strtok_r/strsep関数の使い方を紹介していきます.
strtok関数の使い方
strtok関数の使い方は以下のコードになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <string.h> int main(void) { char *p, *token; char str[] = "abc,defg,h"; char str2[] = "abc,,de"; p = str; printf("str = %s\n", str); while ((token = strtok(p, ",")) != NULL) { printf("token = %s\n", token); p = NULL; } p = str2; printf("str2 = %s\n", str2); while ((token = strtok(p, ",")) != NULL) { printf("token = %s\n", token); p = NULL; } return 0; } |
実行結果は以下になります.
文字列をトークンに分割できていること,str2の空のフィールド(「abc,,de」の「,,」の間の部分)は読み飛ばしていることがわかります.
1 2 3 4 5 6 7 8 9 |
$ gcc strtok.c $ a.out str = abc,defg,h token = abc token = defg token = h str2 = abc,,de token = abc token = de |
strtok_r関数の使い方
strtok_r関数の使い方は以下のコードになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <string.h> int main(void) { char *p, *token, *saveptr; char str[] = "abc,defg,h"; char str2[] = "abc,,de"; p = str; saveptr = NULL; printf("str = %s\n", str); while ((token = strtok_r(p, ",", &saveptr)) != NULL) { printf("token = %s\n", token); printf("saveptr = %s\n", saveptr); p = NULL; } p = str2; saveptr = NULL; printf("str2 = %s\n", str2); while ((token = strtok_r(p, ",", &saveptr)) != NULL) { printf("token = %s\n", token); printf("saveptr = %s\n", saveptr); p = NULL; } return 0; } |
実行結果は以下になります.
saveptrを利用して正しく文字列をトークンに分割できていることがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ gcc strtok_r.c $ a.out str = abc,defg,h token = abc saveptr = defg,h token = defg saveptr = h token = h saveptr = str2 = abc,,de token = abc saveptr = ,de token = de saveptr = |
strsep関数の使い方
strsep関数の使い方は以下のコードになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <string.h> int main(void) { char *p, *token; char str[] = "abc,defg,h"; char str2[] = "abc,,de"; p = str; printf("str = %s\n", str); while ((token = strsep(&p, ",")) != NULL) { printf("token = %s\n", token); } p = str2; printf("str2 = %s\n", str2); while ((token = strsep(&p, ",")) != NULL) { printf("token = %s\n", token); } return 0; } |
実行結果は以下になります.
strtok/strtok_r関数とは異なり,strsep関数は空のフィールドをtokenとして分割できていることがわかります(9行目の「token =」).
1 2 3 4 5 6 7 8 9 10 |
$ gcc strsep.c $ a.out str = abc,defg,h token = abc token = defg token = h str2 = abc,,de token = abc token = token = de |
strtok/strtok_r/strsep関数の自作関数
strtok/strtok_r/strsep関数の自作関数を紹介します.
strtok/strtok_r/strsep関数は少し使いづらい点がありますので,自作関数のコードを参考にしてあなただけの文字列をトークンに分割する関数を作成して頂いても構いません.
strtok関数の自作関数
strtok関数の自作関数「mystrtok関数」のコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> char *mystrtok(char *str, const char *delim) { char *spanp; char c, sc; char *token; static char *last = NULL; if (!str && !(str = last)) { return NULL; } c = *str++; for (spanp = (char *) delim; (sc = *spanp) != '\0'; spanp++) { if (c == sc) { spanp = (char *) delim; c = *str++; } } if (c == '\0') { last = NULL; return NULL; } token = str - 1; while (1) { c = *str++; spanp = (char *) delim; do { if ((sc = *spanp++) == c) { if (c == '\0') { str = NULL; } else { str[-1] = '\0'; } last = str; return token; } } while (sc != '\0'); } /* not reached. */ } int main(void) { char *p, *token; char str[] = "abc,defg,h"; char str2[] = "abc,,de"; p = str; printf("str = %s\n", str); while ((token = mystrtok(p, ",")) != NULL) { printf("token = %s\n", token); p = NULL; } p = str2; printf("str2 = %s\n", str2); while ((token = mystrtok(p, ",")) != NULL) { printf("token = %s\n", token); p = NULL; } return 0; } |
実行結果は以下にになります.
strtok関数と同じ動作になっていることがわかります.
1 2 3 4 5 6 7 8 9 |
$ gcc mystrtok.c $ a.out str = abc,defg,h token = abc token = defg token = h str2 = abc,,de token = abc token = de |
strtok_r関数の自作関数
strtok_r関数の自作関数「mystrtok_r関数」のコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> char *mystrtok_r(char *str, const char *delim, char **saveptr) { char *spanp; char c, sc; char *token; if (!str && !(str = *saveptr)) { return NULL; } c = *str++; for (spanp = (char *) delim; (sc = *spanp) != '\0'; spanp++) { if (c == sc) { spanp = (char *) delim; c = *str++; } } if (c == '\0') { *saveptr = NULL; return NULL; } token = str - 1; while (1) { c = *str++; spanp = (char *) delim; do { if ((sc = *spanp++) == c) { if (c == '\0') { str = NULL; } else { str[-1] = '\0'; } if (str) { *saveptr = str; } else { *saveptr = &spanp[-1]; } return token; } } while (sc != '\0'); } /* not reached. */ } int main(void) { char *p, *token, *saveptr; char str[] = "abc,defg,h"; char str2[] = "abc,,de"; p = str; saveptr = NULL; printf("str = %s\n", str); while ((token = mystrtok_r(p, ",", &saveptr)) != NULL) { printf("token = %s\n", token); printf("saveptr = %s\n", saveptr); p = NULL; } p = str2; saveptr = NULL; printf("str2 = %s\n", str2); while ((token = mystrtok_r(p, ",", &saveptr)) != NULL) { printf("token = %s\n", token); printf("saveptr = %s\n", saveptr); p = NULL; } return 0; } |
実行結果は以下になります.
strtok_r関数と同じ動作になっていることがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ gcc mystrtok_r.c $ a.out str = abc,defg,h token = abc saveptr = defg,h token = defg saveptr = h token = h saveptr = str2 = abc,,de token = abc saveptr = ,de token = de saveptr = |
strsep関数の自作関数
strsep関数の自作関数「mystrsep関数」のコードは以下になります.
mystrsep関数では,strpbrk関数の自作関数「mystrpbrk関数」を呼び出しています.
strpbrk関数は,文字列からバイト集合(文字列)を探して,最初に発見したバイトのポインタを返す関数です.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> char *mystrpbrk(const char *s, const char *accept) { const char *p, *q; for (p = s; *p != '\0'; p++) { for (q = accept; *q != '\0'; q++) { if (*p == *q) { return (char *) p; } } } return NULL; } char *mystrsep(char **stringp, const char *delim) { char *sbegin = *stringp; char *send; if (sbegin == NULL) { return NULL; } if ((send = mystrpbrk(sbegin, delim)) != NULL) { *send++ = '\0'; } *stringp = send; return sbegin; } int main(void) { char *p, *token; char str[] = "abc,defg,h"; char str2[] = "abc,,de"; p = str; printf("str = %s\n", str); while ((token = mystrsep(&p, ",")) != NULL) { printf("token = %s\n", token); } p = str2; printf("str2 = %s\n", str2); while ((token = mystrsep(&p, ",")) != NULL) { printf("token = %s\n", token); } return 0; } |
実行結果は以下になります.
strsep関数と同じ動作になっていることがわかります.
1 2 3 4 5 6 7 8 9 10 |
$ gcc mystrsep.c $ a.out str = abc,defg,h token = abc token = defg token = h str2 = abc,,de token = abc token = token = de |
まとめ
C言語のstrtok/strtok_r/strsep関数の注意点と使い方,自作関数を紹介しました.
strtok/strtok_r/strsep関数は結構難しいので,使い方を正しく理解しましょう.
本記事のコードでは,文字列をトークンに分割する区切り文字はカンマ「,」に設定しています.
文字列(データ)をカンマで区切って管理するCSVファイルの読み書き方法を知りたいあなたはこちらからどうぞ.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!