文字列の検索2

strstr関数

strchr関数は文字列中から「文字」を検索しますが、strstr関数は文字列中から「文字列」を検索します。

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


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

int main()
{
    const char* s1 = "I have a pen.";
    const char* s2 = "en";

    //文字列s1から文字列s2を検索
    char* p = strstr(s1, s2);

    if (p == NULL)
    {
        printf("[%s]は[%s]中にありません。", s2, s1);
    }
    else
    {
        printf("[%s]は[%s]の%d文字目にあります。", s2, s1, p - s1);
    }
    getchar();
}
[en]は[I have a pen.]の10文字目にあります。
char *strstr(
 const char *s1,
 const char *s2,
);
文字列s1内の文字列s2の位置をポインタで返す。
存在しない場合はNULLを返す。

strchr関数の第二引数が文字から文字列になったものです。
使い方もほとんど同じです。

_mbsstr関数

_mbsstr関数はstrstr関数のマルチバイト版対応版です。
_mbschr関数を参照してください。
(ただし、おそらくVisual Studio専用です)

strstr関数の第二引数は文字列を指定するので、これでもマルチバイト文字の検索ができるようにも見えます。
しかしstrstr関数は第一引数をシングルバイト文字(1バイト文字)として扱うため、正しく検索できません。


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

//文字列を16進数で表示
void ShowHex(const char* s)
{
    printf("%s\n", s);
    while (*s) {
        printf("%02X ", *s++);
    }
    printf("\n");
}

int main()
{
    const char* s1 = "初学者";
    const char* s2 = "炎";

    char* p = strstr(s1, s2);

    if (p == NULL)
    {
        printf("[%s]は[%s]中にありません。\n", s2, s1);
    }
    else
    {
        printf("[%s]は[%s]の%dバイト目にあります。\n", s2, s1, p - s1);
    }

    printf("\n");

    ShowHex(s1);
    ShowHex(s2);

    getchar();
}
[炎]は[初学者]の1バイト目にあります。

初学者
8F 89 8A 77 8E D2
炎
89 8A

「初学者」という文字列の中に「炎」という文字はないのに、strstr関数で検索すると存在することになっています。

それぞれの文字を数値(16進数)で見ると、「初」は「8F89」、「学」は「8A77」、「者」は「8ED2」で表されています。
「炎」は「898A」で表されています。
(Shift_JISの場合)

正確に検索するには「8F89」「8A77」「8ED2」の三つを検索対象にする必要があります。
しかしstrstr関数はマルチバイト文字を考慮しないため、「8F89」の次は1バイトずらして「898A」を検索対象にします。
これが「炎」の「898A」と一致してしまうため、正しい検索ができません。

_mbsstr関数は正確にマルチバイト文字を判定するため、こういった問題は起こりません。

末尾から文字列を検索したい(strrstr関数?)

strchr関数が先頭から文字を検索するのに対して、strrchr関数は末尾から検索します。
strstr関数は先頭から文字列を検索しますが、末尾から検索する関数はC標準関数にはありません。
(strrstr関数みたいなものはない)
末尾から検索する場合は自分で実装する必要があります。

以下にそのサンプルコードを示します。


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

//文字列から文字列を末尾から検索する
//str: 探される文字列
//search: 探す文字列
//戻り値: 見つかった位置のポインタ
//	見つからなければNULL
char* MyStrrstr(const char* str, const char* search)
{
    //どちらかがNULLポインタなら
    //検索失敗
    if (!str || !search)
        return NULL;

    const size_t str_len = strlen(str);
    const size_t search_len = strlen(search);
    
    //探す文字列より探される文字列のほうが
    //短い場合は絶対に見つからない
    if (str_len < search_len)
        return NULL;

    //現在の検索位置を格納する変数
    //strの末尾 - searchの文字数の位置にセット
    char* p = (char*)str + str_len - search_len;

    //pがstrの先頭位置に到達するまでループ
    while (p >= str)
    {
        //pからsearchを検索
        if (strncmp(p, search, search_len) == 0)
        {
            return p;
        }
        p--;
    }

    //見つからなかった場合
    return NULL;
}

int main()
{
    const char* s1 = "What do you want to eat?";
    const char* s2 = "at";

    char* p = MyStrrstr(s1, s2);

    if (p == NULL)
    {
        printf("[%s]は[%s]中にありません。", s2, s1);
    }
    else
    {
        printf("[%s]は[%s]の%d文字目にあります。", s2, s1, p - s1);
    }
    getchar();
}
[at]は[What do you want to eat?]の21文字目にあります。

メモリから文字列の検索(memstr関数?)

memchr関数はメモリから文字を検索しますが、文字列を検索する関数はC言語標準関数にはありません。
以下は実装のサンプルコードです。


#include <stdio.h>
#include <string.h> //今回の自作関数自体には必要ない

//メモリからバイト列を検索する
//buf: 探すメモリ領域
//bufSize: bufのサイズ
//search: 探すバイト列
//searchSize: searchのサイズ
//reverse: 真(0以外)なら末尾から検索
//戻り値: 見つかった位置のポインタ
//  見つからなければNULL
void* MyMemstr(const void* buf, size_t bufSize, const void* search, size_t searchSize, char reverse)
{
    //どちらかがNULLポインタ
    //またはsearchSizeが0なら
    //検索失敗
    if (!buf || !search || searchSize == 0)
        return NULL;

    //探す文字列より探される文字列のほうが
    //短い場合は絶対に見つからない
    if (bufSize < searchSize)
        return NULL;

    //const void*型のままではポインタ演算ができない
    //(サイズがわからない)ので
    //unsigned char*型に変換
    //位置は固定で良いのでconst
    const unsigned char* const pBuf = (const unsigned char* const)buf;
    const unsigned char* const pSearch = (const unsigned char* const)search;

    const size_t maxCount = bufSize - searchSize + 1; //最大検索回数
    size_t count = 0; //現在の検索回数
    size_t index; //現在の検索位置

    //reverseが真ならindexの位置を
    //末尾-searchSizeにセット
    if (reverse) {
        //indexの位置移動用に
        //引数reverseを再利用
        reverse = -1;
        index = bufSize - searchSize;
    }
    else {
        reverse = 1;
        index = 0;
    }

    char mismatch = 0; //検索不一致フラグ

    //maxCountに達するまで検索
    while (count < maxCount) {
        //フラグをクリア
        mismatch = 0;

        //pBuf+indexの位置からpSearchを検索
        for (int n = 0; n < searchSize; n++) {
            if (pBuf[index + n] != pSearch[n]) {
                //一致しなかった
                mismatch = 1;
                break;
            }
        }
        //全て一致したら
        //その位置を返して処理終了
        if (!mismatch)
            return (void*)(pBuf + index);

        count++;
        index += reverse;
    }

    return NULL;
}

//画面表示用
//引数はMyMemstrと同じ
void Print(const void* buf, size_t bufSize, const void* search, size_t searchSize, char reverse)
{
    if (!buf || !search)
    {
        printf("NULLはダメ\n");
        return;
    }

    unsigned char* pBuf = (unsigned char*)buf;
    unsigned char* pSearch = (unsigned char*)search;

    printf("探されるバイト列:\n");
    for (size_t i = 0; i < bufSize; i++) {
        printf("%02X ", pBuf[i]);
    }
    printf("\n探すバイト列(%s順検索):\n", reverse ? "降" : "昇");
    for (size_t i = 0; i < searchSize; i++) {
        printf("%02X ", pSearch[i]);
    }

    char* p = MyMemstr(buf, bufSize, search, searchSize, reverse);

    if (p == NULL)
    {
        printf("\n見つかりませんでした。\n\n");
    }
    else
    {
        printf("\n%d番目にあります。\n\n", p - buf);
    }
}

int main()
{
    const char* s1 = "abcde\0abcde";
    const char* s2 = "cd";

    Print(s1, 11, s2, strlen(s2), 0);
    Print(s1, 11, s2, strlen(s2), 1);

	getchar();
}
探されるバイト列:
61 62 63 64 65 00 61 62 63 64 65
探すバイト列(昇順検索):
63 64
2番目にあります。

探されるバイト列:
61 62 63 64 65 00 61 62 63 64 65
探すバイト列(降順検索):
63 64
8番目にあります。

少し長く見えますが、関数内の半分くらいは処理の前準備です。
実際の比較処理はループ文内で、ポインタbufの先頭(または末尾)からポインタsearchの一文字目を探し、見つかったら二文字目同士、三文字目同士と順に比較していきます。
全てが一致したら(不一致と判断されなかったら)そのポインタの位置を返しています。

strstr関数は途中にNULL文字があるとそれ以上は検索できませんが、自作のMyMemstr関数ではそれが出来ています。
ついでに、第五引数を真(0以外)にすることで末尾からも検索できるようにしています。