C言語のきほん|文字列内を探す(strchr、strstr)

文字列の中から、ほしい文字とことばを見つけ出す。strchr と strstr で検索の基本をしっかり身につけよう

C言語で文字列を扱っていると、文字列そのものを表示したり、コピーしたり、比較したりするだけでなく、文字列の中に特定の文字や語句が含まれているかを調べたい場面がよくあります。

たとえば、次のような処理です。

  • メールアドレスに @ が含まれているか調べる
  • ファイル名に . がある位置を探す
  • 文章の中に error という語があるか調べる
  • 入力文字列に特定のキーワードが含まれているか確認する

このようなときに役立つのが、標準ライブラリ関数の strchr と strstr です。

strchr は、文字列の中から特定の1文字を探す関数です。
一方の strstr は、文字列の中から特定の部分文字列を探す関数です。

どちらも、見つかった場所を表すポインタを返します。
そのため、単に「見つかった」「見つからなかった」だけでなく、どこにあるのかまで調べられるのが大きな特徴です。

この節では、strchr と strstr の使い方だけでなく、

  • 返却値の意味
  • 見つからなかったときの扱い
  • 位置の求め方
  • ポインタを返すとはどういうことか
  • 実際の検索や置換への応用

まで、順番にていねいに見ていきます。

文字列の中を探せるようになると、C言語の文字列処理は一気に実用的になります。
ここで基本をしっかり押さえておきましょう。

文字列内を探すとは

文字列内を探すとは、ある文字列の中に、特定の文字や語句が含まれているかを調べ、その位置を見つけることです。

たとえば、文字列

Hello world!

の中には、

  • 文字 o がある
  • 文字列 world がある
  • 文字 x はない
  • 文字列 cat はない

というようなことが言えます。

このような検索を自分で1文字ずつ書いて調べることもできますが、標準ライブラリ関数を使うと、ずっと簡単に書けます。

関数名探す対象
strchr1文字
strstr部分文字列

つまり、

  • 1文字を探したいなら strchr
  • 単語や語句を探したいなら strstr

と覚えるとわかりやすいです。

strchr 関数とは

strchr 関数は、文字列の中から特定の文字を探し、その位置を指すポインタを返す関数です。
同じ文字が複数回出てきても、返されるのは最初に見つかった位置だけです。

関数宣言

#include <string.h>

char *strchr(const char *s, int c);

機能

s が指す文字列の中で、c を char 型に変換した文字が最初に現れる位置を探します。
なお、ナル文字 \0 も文字列の一部として扱われます。

返却値

条件返却値
見つかったその文字を指すポインタ
見つからないNULL

使用例

char str[] = "Hello world!";
char *pos = strchr(str, 'o');

printf("位置:%d\n", (int)(pos - str));

この例では、最初の o は先頭から4文字目にあるので、4 が表示されます。

strchr の動き

strchr は、文字列の先頭から順に1文字ずつ調べていきます。
そして、指定した文字が最初に見つかった時点で、その位置へのポインタを返します。

たとえば、Hello world! の中で o を探すときの流れは次のようになります。

位置文字判定
0H不一致
1e不一致
2l不一致
3l不一致
4o一致 → ここを返す

このように、strchr は最初に一致した場所で検索を終えます。

strchr の位置を求めるしくみ

strchr の返却値は「位置そのもの」ではなく「その文字の場所を指すポインタ」です。
そのため、先頭から何文字目にあるかを数値で知りたいときは、先頭ポインタとの差を取ります。

pos - str

これによって、pos が str から何文字先にあるかがわかります。

この図では、文字列 Hello world! の中から文字 o を探し、最初に見つかった位置を pos が指していることを表しています。
str は文字列の先頭を指しているので、pos - str を計算すると、その文字が先頭から何文字目にあるかがわかります。

strchr を使うときの注意点

strchr を使うときは、次の点を意識しておくと安全です。

注意点内容
見つからない場合があるNULL が返る
最初の1回分だけ返す2回目以降は返さない
返るのはポインタ数字そのものではない
\0 も検索対象になるナル文字も探せる

特に大事なのは、NULL の確認です。

char *pos = strchr(str, 'x');

if (pos != NULL) {
    printf("見つかりました。\n");
} else {
    printf("見つかりませんでした。\n");
}

このように、まず見つかったかどうかを確認してから使うのが基本です。

strstr 関数とは

strstr 関数は、文字列の中から特定の部分文字列を探す関数です。
strchr が1文字検索なのに対して、strstr は文字列検索を行います。

関数宣言

#include <string.h>

char *strstr(const char *s1, const char *s2);

機能

s1 が指す文字列の中で、s2 が指す文字列が最初に現れる位置を探します。

返却値

条件返却値
見つかった見つかった部分文字列の先頭を指すポインタ
見つからないNULL
s2 が空文字列s1 を返す

使用例

char str[] = "Hello world!";
char *pos = strstr(str, "world");

printf("位置:%d\n", (int)(pos - str));

この例では、world は先頭から6文字目に始まるので、6 が表示されます。

strstr の動き

strstr は、s1 の先頭から順に見ていき、「ここから先が s2 と一致するか」を調べます。
つまり、1文字ではなく、まとまりとして一致するかを見ています。

たとえば、文字列

C language is fun

の中から

lang

を探すときは、最初の C からでは一致せず、空白からでも一致せず、l の位置から見たときに lang が一致するので、その位置が返されます。

この図では、Hello world! の中から world を探しています。
strstr は、見つかった部分文字列全体ではなく、その先頭位置を返します。
そのため、pos - str を計算することで、開始位置が 6 だとわかります。

strchr と strstr の違い

この2つの違いを表で整理すると、次のようになります。

項目strchrstrstr
探す対象1文字部分文字列
返すもの見つかった文字の位置見つかった部分文字列の先頭位置
見つからない場合NULLNULL

つまり、

  • 文字1つを探すなら strchr
  • 単語や語句を探すなら strstr

という使い分けです。

シンプルなサンプルプログラム

1つの文字列の中から、特定の文字と特定の語句を探す例です。

ファイル名:12_8_1.c

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

int main(void)
{
    char text[] = "Learning C language is fun.";
    char *pos1;
    char *pos2;

    /* 文字 'a' を探す */
    pos1 = strchr(text, 'a');
    if (pos1 != NULL) {
        printf("文字 a は %d 文字目に見つかりました。\n", (int)(pos1 - text));
    } else {
        puts("文字 a は見つかりませんでした。");
    }

    /* 部分文字列 \"lang\" を探す */
    pos2 = strstr(text, "lang");
    if (pos2 != NULL) {
        printf("文字列 lang は %d 文字目に見つかりました。\n", (int)(pos2 - text));
    } else {
        puts("文字列 lang は見つかりませんでした。");
    }

    return 0;
}

このプログラムでは、まず strchr で a を探し、次に strstr で lang を探しています。
どちらも、見つかった位置を先頭との差で求めて表示しています。

ポインタを返すことの便利さ

strchr や strstr は、見つかった位置へのポインタを返すので、その位置からさらに処理を続けることができます。

たとえば、

  • 見つかった位置から後ろだけ表示する
  • 見つかった位置までの長さを求める
  • その位置を境に文字列を分ける
  • 同じ文字列の中で次の検索を続ける

といった応用ができます。

これは、単に「ある・ない」を調べるだけの関数ではなく、次の処理につなげやすい関数だということです。

実践問題

1月から12月までの英語の月名について、それぞれの名前に文字 r が含まれているかどうかを調べて表示するプログラムを作成してください。

解答例

ファイル名:12_8_2.c

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

int main(void)
{
    char months[][20] = {
        "January", "February", "March", "April",
        "May", "June", "July", "August",
        "September", "October", "November", "December"
    };

    int count = sizeof(months) / sizeof(months[0]);

    puts("月の名前と文字 r の有無");

    for (int i = 0; i < count; i++) {
        if (strchr(months[i], 'r') != NULL) {
            printf("%2d月: %-10s  r を含む\n", i + 1, months[i]);
        } else {
            printf("%2d月: %-10s  r を含まない\n", i + 1, months[i]);
        }
    }

    return 0;
}

解説

この問題では、各月名の中に文字 r があるかどうかを strchr で調べています。
見つかれば NULL 以外が返るので、「含む」と判断できます。
複数の文字列を順に検索する練習としてちょうどよい問題です。

実践問題

次の野菜名の一覧について、辞書順で一番大きい名前と、文字列の中に a が最初に現れる位置が最も前の野菜名を調べて表示してください。

野菜の一覧は次の通りです。

"Tomato", "Carrot", "Cabbage", "Pumpkin", "Onion",
"Radish", "Spinach", "Potato", "Lettuce", "Peas"

辞書順の比較には strcmp、文字 a の位置の検索には strchr を使ってください。

解答例

ファイル名:12_8_3.c

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

int main(void)
{
    char vegetables[][20] = {
        "Tomato", "Carrot", "Cabbage", "Pumpkin", "Onion",
        "Radish", "Spinach", "Potato", "Lettuce", "Peas"
    };

    int count = sizeof(vegetables) / sizeof(vegetables[0]);
    int max_index = 0;
    int best_index = -1;
    int best_pos = 1000;

    for (int i = 1; i < count; i++) {
        if (strcmp(vegetables[i], vegetables[max_index]) > 0) {
            max_index = i;
        }
    }

    for (int i = 0; i < count; i++) {
        char *pos = strchr(vegetables[i], 'a');
        if (pos != NULL) {
            int index = (int)(pos - vegetables[i]);
            if (index < best_pos) {
                best_pos = index;
                best_index = i;
            }
        }
    }

    printf("辞書順で一番大きい名前の野菜: %s\n", vegetables[max_index]);

    if (best_index != -1) {
        printf("文字 a が最も前に現れる野菜: %s\n", vegetables[best_index]);
    } else {
        puts("文字 a を含む野菜はありません。");
    }

    return 0;
}

解説

前半では strcmp を使って辞書順の最大値を調べています。
後半では strchr で a の位置を求め、先頭に最も近いものを選んでいます。
検索関数と比較関数を組み合わせる練習になります。

実践問題

キーボードから文字列を複数回入力し、1つの文字型配列に順次連結してください。
ただし、入力文字列に ? が含まれていた場合は、その入力は連結せずに無視してください。

条件は次の通りです。

  • 配列サイズは 30文字
  • 30文字に達したら終了
  • 入力には fgets を使う
  • 連結には strncat を使う
  • ? の有無の判定には strchr を使う

解答例

ファイル名:12_8_4.c

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

int main(void)
{
    char result[31] = "";
    char input[100];

    puts("文字列を繰り返し入力してください。(30文字で終了します)");
    puts("? を含む入力は連結しません。");

    while (strlen(result) < 30) {
        printf("追加する文字列> ");

        if (fgets(input, sizeof(input), stdin) == NULL) {
            break;
        }

        size_t len = strlen(input);
        if (len > 0 && input[len - 1] == '\n') {
            input[len - 1] = '\0';
        }

        if (strchr(input, '?') != NULL) {
            puts("? が含まれているので、この入力は無視します。");
            continue;
        }

        size_t remain = 30 - strlen(result);
        strncat(result, input, remain);
    }

    puts("完成した文字列:");
    printf("%s(%zu文字)\n", result, strlen(result));

    return 0;
}

解説

この問題では、入力文字列に ? があるかを strchr で調べています。
あればその入力はスキップし、なければ strncat で連結します。
検索と連結を組み合わせた実践的な例です。

実践問題

次の文字列中の単語 red をすべて blue に置換し、結果を新しい文字列配列に格納してください。

元の文字列は次の通りです。

A red book and a red pen were placed on the red desk.

検索には strstr を使い、現在位置を示すポインタを更新しながら処理してください。

解答例

ファイル名:12_8_5.c

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

int main(void)
{
    char src[] = "A red book and a red pen were placed on the red desk.";
    char dest[200] = "";
    char *current = src;
    char *found;

    while ((found = strstr(current, "red")) != NULL) {
        strncat(dest, current, (size_t)(found - current));
        strcat(dest, "blue");
        current = found + 3;
    }

    strcat(dest, current);

    puts("元の文字列:");
    puts(src);
    puts("");
    puts("置換後の文字列:");
    puts(dest);

    return 0;
}

解説

この問題では、strstr で red の位置を順に探しながら、見つかった位置までの文字列を dest にコピーし、そのあと blue を追加しています。
current を更新して次の検索位置を進めていくのがポイントです。
strstr は置換処理の基礎としてとてもよく使われます。

実践問題

次の書籍タイトル一覧について、部分文字列 Data を含むタイトルだけを表示してください。

char books[][40] = {
    "C Programming Basics",
    "Linux Start Guide",
    "Data Structure",
    "Network Basics",
    "Database Intro",
    "Web Design"
};

検索には strstr を使ってください。

解答例

ファイル名:12_8_6.c

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

int main(void)
{
    char books[][40] = {
        "C Programming Basics",
        "Linux Start Guide",
        "Data Structure",
        "Network Basics",
        "Database Intro",
        "Web Design"
    };

    int count = sizeof(books) / sizeof(books[0]);

    puts("Data を含むタイトル");

    for (int i = 0; i < count; i++) {
        if (strstr(books[i], "Data") != NULL) {
            puts(books[i]);
        }
    }

    return 0;
}

解説

この問題では、各タイトルに Data が含まれているかを strstr で調べています。
見つかれば NULL 以外が返るので、そのタイトルを表示します。
部分一致で絞り込みを行う基本練習として使いやすい問題です。

strchr と strstr を使うときの基本の流れ

この2つの関数を使うときは、次の流れで考えると整理しやすいです。

手順内容
1何を探したいか決める
21文字なら strchr、文字列なら strstr を選ぶ
3返却値が NULL かどうか確認する
4見つかったら位置やその後の文字列に活用する

この流れを意識すると、検索処理がとても書きやすくなります。

よくある使い方

やりたいこと使う関数
特定の記号があるか調べるstrchr@ や . の有無を確認する
区切り文字の位置を知るstrchr/ や : の位置を探す
単語が含まれるか調べるstrstrerror があるか確認する
文字列を置換する準備をするstrstrcat を rabbit に置換する
一致した位置から続きを処理するstrchr / strstrその位置から再検索する

strchr と strstr を学ぶ意味

この2つの関数は、文字列を単に表示するだけでなく、中身を調べるための大切な関数です。

学べること内容
文字検索の基本1文字を探す考え方
部分文字列検索の基本単語や語句を探す考え方
NULL 判定見つからない場合の安全な扱い
ポインタ差位置を数値として求める方法
応用処理置換、抽出、判定への応用

strchr と strstr を使えるようになると、文字列処理の幅はかなり広がります。
特定の記号の有無を調べたり、単語を見つけたり、見つかった場所からさらに処理を進めたりできるようになるので、実用的なプログラムを書く力がぐっと高まります。