文字列の中から特定の文字や語句を探す処理は、入力解析やログ抽出など多くの場面で登場します。
本記事ではC言語標準のstrchr
とstrstr
を中心に、基本の使い方から返り値の扱い、見つからない場合の分岐、複数回見つける方法や注意点までを丁寧に解説します。
返り値のポインタとNULL
の扱いが最大の要点です。
文字列検索の基本
必要なヘッダ
これらの関数はstring.h
に宣言されています。
サンプルの出力にはstdio.h
、ポインタ差を出力する場合はstddef.h
が必要です。
#include <string.h> // strchr, strstr
#include <stdio.h> // printf (サンプル出力用)
#include <stddef.h> // ptrdiff_t (ポインタ差の型)
必ずヌル終端された文字列を渡すことが前提です。
配列の一部だけを検索したい場合はmemchr
など別系統の関数を検討します。
strchrとstrstrの違い
1文字を探すのがstrchr
、部分文字列(複数文字)を探すのがstrstr
です。
どちらも見つかった最初の位置へのポインタを返すか、見つからなければNULL
を返します。
項目 | strchr | strstr |
---|---|---|
探す対象 | 1文字(cとして渡す) | 文字列(needle) |
検索対象 | ヌル終端文字列(s) | ヌル終端文字列(haystack) |
返り値 | 見つかった文字へのポインタ or NULL | 見つかった部分文字列の先頭へのポインタ or NULL |
特殊扱い | cが'\0' なら終端位置を返す | needleが空文字ならhaystack先頭を返す |
大文字小文字 | 区別する | 区別する |
両者とも線形走査のO(n)アルゴリズムです。
高速化が必要ならアルゴリズムの工夫(例えばBoyer–Moore系)やライブラリの活用を検討します。
返り値とNULLの意味
返り値は「見つかった位置」へのポインタです。
見つからないとNULL
が返るため、必ずNULL
チェックを行ってから参照やインデックス計算をしてください。
const char *s = "example";
const char *p = strchr(s, 'm');
if (p != NULL) {
// pはsの中の'm'を指す
} else {
// 見つからなかった
}
ヌル終端の前提
これらの関数はヌル文字'\0'
に遭遇するまで走査します。
終端がないバッファや、途中に意図しない'\0'
があるデータでは期待通りに動きません。
未終端のメモリ領域を渡すのは未定義動作です。
strchrの使い方
関数の書式と引数
// C標準
char *strchr(const char *s, int c);
s
: 検索対象のヌル終端文字列c
: 探す文字(整数型)。unsigned charとして表現可能な値か'\0'
を渡します
実装によってはchar
が符号付きのため、拡張文字(0x80以上)を扱う場合は(unsigned char)
にキャストしてからint
に昇格させると安全です。
基本のサンプル
#include <string.h>
#include <stdio.h>
#include <stddef.h>
int main(void) {
char s[] = "Hello, world";
char *p = strchr(s, 'o'); // 最初の'o'を探す
if (p != NULL) {
ptrdiff_t idx = p - s; // インデックス(0始まり)
printf("found '%c' at index %td, substring: \"%s\"\n", *p, idx, p);
}
return 0;
}
found 'o' at index 4, substring: "o, world"
最初に見つかった位置を得る
strchr
は先頭から最初に一致した位置のみを返します。
同じ文字が複数回出現しても、返り値は常に最初の一致です。
#include <string.h>
#include <stdio.h>
#include <stddef.h>
int main(void) {
const char *s = "bookkeeper";
const char *p = strchr(s, 'e'); // 'e'は何度か出るが最初の'e'のみ
if (p) {
printf("first 'e' index: %td\n", p - s);
}
return 0;
}
first 'e' index: 5
見つからない場合
見つからないとNULL
が返ります。
NULLのまま参照しないでください。
#include <string.h>
#include <stdio.h>
int main(void) {
const char *s = "abcdef";
const char *p = strchr(s, 'x');
if (p == NULL) {
printf("not found\n");
}
return 0;
}
not found
インデックスの求め方
ポインタ差p - s
で0始まりの位置が得られます。
型はptrdiff_t
です。
#include <string.h>
#include <stdio.h>
#include <stddef.h>
int main(void) {
const char *s = "ABCDE";
const char *p = strchr(s, 'D');
if (p) {
ptrdiff_t idx = p - s;
printf("index: %td\n", idx);
}
return 0;
}
index: 3
特殊ケース
- 終端文字
'\0'
を探す 文字列の末尾(終端)を指すポインタが返ります。
- 改行など制御文字
'\n'
や'\t'
も通常の文字定数として検索できます。- 拡張文字
ロケールや実装で
char
が符号付きの場合、strchr(s, (unsigned char)ch)
の形が安全です。- 未終端データ
バイナリデータや途中に
'\0'
を含むデータではstrchr
は途中で止まります。長さが分かっている場合はmemchr
の検討をおすすめします。
#include <string.h>
#include <stdio.h>
int main(void) {
const char *s = "abc";
const char *p = strchr(s, '#include <string.h>
#include <stdio.h>
int main(void) {
const char *s = "abc";
const char *p = strchr(s, '\0'); // 終端を探す
if (p) {
printf("length via strchr: %ld\n", (long)(p - s)); // strlenと同じ
}
return 0;
}
'); // 終端を探す
if (p) {
printf("length via strchr: %ld\n", (long)(p - s)); // strlenと同じ
}
return 0;
}
length via strchr: 3
大文字小文字は区別
strchr
はケースセンシティブです。
#include <string.h>
#include <stdio.h>
int main(void) {
const char *s = "Abc";
printf("%s\n", strchr(s, 'a') ? "found" : "not found"); // 'A' != 'a'
printf("%s\n", strchr(s, 'A') ? "found" : "not found");
return 0;
}
not found
found
大文字小文字を無視したい場合は、POSIXのstrcasestr
(非標準)を使うか、文字を都度tolower
/toupper
で正規化して比較する方法があります(詳細は別記事で扱います)。
strstrの使い方
関数の書式と引数
// C標準
char *strstr(const char *haystack, const char *needle);
haystack
: 検索対象の文字列needle
: 探す部分文字列(空文字も可)
基本のサンプル
#include <string.h>
#include <stdio.h>
#include <stddef.h>
int main(void) {
const char *s = "banana bread";
const char *p = strstr(s, "ana"); // 最初の"ana"
if (p) {
printf("found at index %td: \"%s\"\n", p - s, p);
}
return 0;
}
found at index 1: "anana bread"
部分文字列の最初の位置を得る
返り値ポインタ差p - s
で先頭のインデックスが求まります。
以降を表示すると、見つかった位置から末尾までが確認できます。
needleが空文字のとき
needle
が""
なら常にhaystack
先頭のポインタが返ります。
#include <string.h>
#include <stdio.h>
#include <stddef.h>
int main(void) {
const char *s = "abc";
const char *p = strstr(s, ""); // 空文字
printf("index: %td\n", p - s); // 常に0
return 0;
}
index: 0
見つからない場合
見つからないとNULL
が返ります。
#include <string.h>
#include <stdio.h>
int main(void) {
const char *s = "pineapple";
printf("%s\n", strstr(s, "pear") ? "found" : "not found");
return 0;
}
not found
大文字小文字は区別
strstr
もケースセンシティブです。
#include <string.h>
#include <stdio.h>
int main(void) {
const char *s = "Hello World";
printf("%s\n", strstr(s, "world") ? "found" : "not found");
printf("%s\n", strstr(s, "World") ? "found" : "not found");
return 0;
}
not found
found
ケース無視検索はPOSIXのstrcasestr
や、自前で小文字化してからstrstr
を使うなどの方法があります(本記事では詳細割愛)。
複数回見つける方法
次の位置から繰り返しstrstr
を呼びます。
重なりを許すか否かで進め方が異なります。
#include <string.h>
#include <stdio.h>
#include <stddef.h>
int main(void) {
const char *s = "bananana";
const char *needle = "ana";
size_t nlen = strlen(needle);
// 1) オーバーラップを許す検索
printf("overlap allowed:\n");
const char *p = s;
while ((p = strstr(p, needle)) != NULL) {
printf(" index %td\n", p - s);
p = p + 1; // 1文字進めて次も探索(重なり許容)
}
// 2) オーバーラップを許さない検索
printf("no overlap:\n");
p = s;
while ((p = strstr(p, needle)) != NULL) {
printf(" index %td\n", p - s);
p = p + nlen; // 見つかった語の分だけスキップ
}
return 0;
}
overlap allowed:
index 1
index 3
index 5
no overlap:
index 1
index 5
よくある用途と注意点
文字の有無を判定する
1文字の出現チェックはstrchr
で簡潔に記述できます。
条件式にそのまま使うと可読性が高まります。
#include <string.h>
#include <stdio.h>
int main(void) {
const char *s = "path/to/file.txt";
if (strchr(s, '/')) {
printf("has directory separator\n");
}
return 0;
}
has directory separator
キーワードを含むか判定する
strstr
でキーワードの包含判定ができます。
先頭一致か部分一致かを要件に応じて使い分けます。
#include <string.h>
#include <stdio.h>
int main(void) {
const char *line = "GET /index.html HTTP/1.1";
if (strstr(line, "HTTP/1.1")) {
printf("HTTP/1.1 request\n");
}
return 0;
}
HTTP/1.1 request
次の位置から検索を続ける
返り値ポインタを基準に1文字進めて再検索するのが基本です。
非重複にしたい場合はneedle
の長さだけスキップします。
#include <string.h>
#include <stdio.h>
int main(void) {
const char *s = "Hello, world";
const char *p = s;
while ((p = strchr(p, 'l')) != NULL) {
printf("found at %ld\n", (long)(p - s));
p = p + 1; // 次の位置から継続
}
return 0;
}
found at 2
found at 3
found at 10
ポインタからインデックスへ変換する
インデックスはptrdiff_t idx = p - s;
で得ます。
配列インデックスに使うなら適宜size_t
にキャストします(負にならないことが前提)。
#include <string.h>
#include <stdio.h>
#include <stddef.h>
int main(void) {
const char *s = "indexing";
const char *p = strchr(s, 'x');
if (p) {
ptrdiff_t idx = p - s; // 標準的なやり方
printf("%td\n", idx); // C99以降の%td指定
}
return 0;
}
4
NULLのまま参照しない
NULLチェックを忘れて*p
やp[0]
に触れると未定義動作です。
見つからなかった分岐を必ず記述します。
#include <string.h>
#include <stdio.h>
int main(void) {
const char *s = "data";
const char *p = strchr(s, 'z');
if (p == NULL) {
printf("safe: not found\n");
// 危険: printf("%c\n", *p); // これはダメ
}
return 0;
}
safe: not found
マルチバイト文字への注意
これらの関数は「バイト列として」検索します。UTF-8やShift_JISのマルチバイト文字を1文字としては扱いません。
strchr
は1バイトの文字しか検索できません。例えばUTF-8の「あ」(3バイト)はstrchr
では探せません。
strstr
はマルチバイトの「並び」を検索できるため、UTF-8の「あ」を含むバイト列は見つけられますが、コードポイントの境界を理解しているわけではありません。
より厳密に国際化対応したい場合はwchar_t
系(wcschr
, wcsstr
)や、ICUなどのライブラリを検討してください(用途により選択)。
まとめ
文字(1バイト)を探すならstrchr
、語句(部分文字列)を探すならstrstr
が基本です。
返り値は「見つかった位置へのポインタ」またはNULL
であり、必ずNULL
チェックを行い、p - s
でインデックスを得られる点を押さえておくと実装が安定します。
加えて、ヌル終端前提、ケースセンシティブ、マルチバイト文字はバイト列として扱われるという性質に注意してください。
これらの基本を正しく使い分けることで、文字列検索処理を安全かつ明瞭に記述できます。