日本語を使用している時にstd::stringのfind系が意図通りに動かない問題のつづき

# はじめに

前回の記事の続きですが、今回はfind系の動きの深堀をしようと思います

前回の記事はこちら

https://qiita.com/f_tsucchy/items/a67b96c07ef638bbe831

# 前回までの内容
前回、マルチバイト文字のダメ文字について説明したのですが、下記の時に想定した位置が返ってこないことについて触れていきたいと思います

```
・2文字以上の文字列を検索
find_last_of：検索文字列のうち最初にヒットした末尾の文字の位置が返る
GetLastPos：先頭の文字の位置が返る

・マルチバイト文字を検索
find_last_of：検索した文字を1byteずつに分割した文字のうち最初にヒットした末尾の文字の位置が返る
GetLastPos：マルチバイトの1byte目の位置が返る

・サイズオーバーかつ一部が一致している文字列を検索
find_last_of：検索文字列のうち最初にヒットした末尾の文字の位置が返る
GetLastPos：std::string::nposが返る
```

## 2文字以上の文字列を検索

まずはじめにこちら

```
find_last_of：検索文字列のうち最初にヒットした末尾の文字の位置が返る
GetLastPos：先頭の文字の位置が返る
```

どういうことかコードを動かしながら見ていきましょう

```C++
int main()
{
	std::string part2 = "cabcbac";

	std::cout << "cbの位置を先頭から検索した結果 位置=" << GetFirstPos( part2, "cb" ) << std::endl;
	std::cout << "cbの位置を末尾から検索した結果 位置=" << GetLastPos( part2, "cb" ) << std::endl;

	std::cout << std::endl;

	std::cout << "cbの位置を先頭から検索した結果(通常のfind) 位置=" << part2.find( "cb", 0 ) << std::endl;
	std::cout << "cbの位置を末尾から検索した結果(通常のfind) 位置=" << part2.find_last_of( "cb" ) << std::endl;

	std::cout << std::endl;
}
```

前回コードは書いているので呼び出し箇所と結果の部分だけ記載していきます

```
cbの位置を先頭から検索した結果 位置=3
cbの位置を末尾から検索した結果 位置=3

cbの位置を先頭から検索した結果(通常のfind) 位置=3
cbの位置を末尾から検索した結果(通常のfind) 位置=6
```

通常のfindは問題なし、find_last_ofは作成した関数と異なる位置が返ってきます
これはfind_last_ofは「cb」で文字列検索すると「cまたはbを後ろから検索して最初に現れた部分を返す」という仕様になっているためです
つまりfind_last_ofは「cb」のように連続した文字列を探したいと思ったときに使用することができません
そのため、末尾の「c」が最初に引っかかったため位置が他と異なる結果になりました

実は「rfind」を使えば回避可能ですが、やっぱりこちらもダメ文字はNG
ダメ文字はダメだった

また、find_last_ofと同様にfind_first_ofも同じような検索結果になります

```C++
int main()
{
	std::string part2 = "cabcbac";
    
    std::cout << "cbの位置を末尾から検索した結果(通常のfind) 位置=" << part2.rfind( "cb" ) << std::endl;
	std::cout << "cbの位置を先頭から検索した結果(通常のfind) 位置=" << part2.find_first_of( "cb", 0 ) << std::endl;
}
```

rfindは想定通りの位置、find_first_ofはlast_find_ofのように作成した関数と異なる位置が返ってきます
find_first_of/find_last_ofは使い方によってはOR検索のように使えますが、やっぱりマルチバイト文字はNGなのでマルチバイト文字を含む場合は実装が必要です
```
cbの位置を末尾から検索した結果(通常のfind) 位置=3
cbの位置を先頭から検索した結果(通常のfind) 位置=0
```

## マルチバイト文字を検索

次はこちら
```
find_last_of：検索した文字を1byteずつに分割した文字のうち最初にヒットした末尾の文字の位置が返る
GetLastPos：マルチバイトの1byte目の位置が返る
```

ここまで理解してる方ならもうおわかりでしょう
find_first_of/find_last_ofはマルチバイト文字も勝手にシングルバイト文字に分解して検索します
実際に動かしてみていきましょう

```C++
int main()
{
	std::string hoge = "C:\\ソうダネ";

	std::cout << "\\の位置を先頭から検索した結果 位置=" << GetFirstPos( hoge, "\\" ) << std::endl;
	std::cout << "\\の位置を末尾から検索した結果 位置=" << GetLastPos( hoge, "\\" ) << std::endl;

	std::cout << std::endl;

	std::cout << "\\の位置を先頭から検索した結果(通常のfind) 位置=" << hoge.find( "\\", 0 ) << std::endl;
	std::cout << "\\の位置を末尾から検索した結果(通常のfind) 位置=" << hoge.rfind( "\\" ) << std::endl;
	std::cout << "\\の位置を先頭から検索した結果(通常のfind) 位置=" << hoge.find_first_of( "\\" ) << std::endl;
	std::cout << "\\の位置を末尾から検索した結果(通常のfind) 位置=" << hoge.find_last_of( "\\" ) << std::endl;

	std::cout << std::endl;

	std::cout << "ダの位置を先頭から検索した結果 位置=" << GetFirstPos( hoge, "ダ" ) << std::endl;
	std::cout << "ダの位置を末尾から検索した結果 位置=" << GetLastPos( hoge, "ダ" ) << std::endl;

	std::cout << std::endl;

	std::cout << "ダの位置を先頭から検索した結果(通常のfind) 位置=" << hoge.find( "ダ", 0 ) << std::endl;
	std::cout << "ダの位置を末尾から検索した結果(通常のfind) 位置=" << hoge.rfind( "ダ" ) << std::endl;
	std::cout << "ダの位置を先頭から検索した結果(通常のfind) 位置=" << hoge.find_first_of( "ダ" ) << std::endl;
	std::cout << "ダの位置を末尾から検索した結果(通常のfind) 位置=" << hoge.find_last_of( "ダ" ) << std::endl;

	std::cout << std::endl;
}
```
結果↓

```
\の位置を先頭から検索した結果 位置=2
\の位置を末尾から検索した結果 位置=2

\の位置を先頭から検索した結果(通常のfind) 位置=2
\の位置を末尾から検索した結果(通常のfind) 位置=4
\の位置を先頭から検索した結果(通常のfind) 位置=2
\の位置を末尾から検索した結果(通常のfind) 位置=4

ダの位置を先頭から検索した結果 位置=7
ダの位置を末尾から検索した結果 位置=7

ダの位置を先頭から検索した結果(通常のfind) 位置=7
ダの位置を末尾から検索した結果(通常のfind) 位置=7
ダの位置を先頭から検索した結果(通常のfind) 位置=3
ダの位置を末尾から検索した結果(通常のfind) 位置=9
```

前回の文字コードの表と照らし合わせてみれば想定した位置が返ってきていないのも納得ですね
rfindでマルチバイト文字列を検索すると「\」はマルチバイト文字もシングルバイトで引っかかった方が判定されていまい、find_last_ofの「ダ」の検索も「ダ」ではなく、「0x83」または「0x5f」のいずれかが引っかかった方で判定されています

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969992/5c05e8e3-a9b9-cc29-d74a-956b35847766.png)


## サイズオーバーかつ一部が一致している文字列を検索
最後はこちら
```
find_last_of：検索文字列のうち最初にヒットした末尾の文字の位置が返る
GetLastPos：std::string::nposが返る
```

ここまで読んできた方はもうおわかりでしょうが、引き続き実行結果を記載していきます

```C++
int main()
{
	std::string hoge = "C:\\ソうダネ";

	// ↑のhogeより長い文字列
	std::string long_hoge = "C:\\ソうダネソうダネ";

	std::cout << "サイズオーバーを先頭から検索した結果 位置=" << GetFirstPos( hoge, long_hoge ) << std::endl;
	std::cout << "サイズオーバーを末尾から検索した結果 位置=" << GetLastPos( hoge, long_hoge ) << std::endl;

	std::cout << std::endl;

	std::cout << "サイズオーバーを先頭から検索した結果(通常のfind) 位置=" << hoge.find( long_hoge, 0 ) << std::endl;
	std::cout << "サイズオーバーを末尾から検索した結果(通常のfind) 位置=" << hoge.rfind( long_hoge ) << std::endl;
	std::cout << "サイズオーバーを先頭から検索した結果(通常のfind) 位置=" << hoge.find_first_of( long_hoge ) << std::endl;
	std::cout << "サイズオーバーを末尾から検索した結果(通常のfind) 位置=" << hoge.find_last_of( long_hoge ) << std::endl;

	std::cout << std::endl;

}
```

結果↓

```
サイズオーバーを先頭から検索した結果 位置=18446744073709551615
サイズオーバーを末尾から検索した結果 位置=18446744073709551615

サイズオーバーを先頭から検索した結果(通常のfind) 位置=18446744073709551615
サイズオーバーを末尾から検索した結果(通常のfind) 位置=18446744073709551615
サイズオーバーを先頭から検索した結果(通常のfind) 位置=0
サイズオーバーを末尾から検索した結果(通常のfind) 位置=10
```

ここまでに記載した内容と同じく、find_first_of/find_last_ofは指定の文字列をシングルバイトに分解して、
そのうちのどれか一つでもヒットしたら位置が返ってきます
完全一致じゃなくても返ってきます

OR検索として使えば使えるのは確かですが、そんなに使う場面もなさそうな気はします
使う場面があったとしてもマルチバイトに対してはよわよわです

# まとめ
find/rfindはダメ文字に弱いという特性はどちらもありますが、ダメ文字を含まなければ大体の文字列検索は問題なく動きます
find_first_of/find_last_ofはOR検索であることを理解して使えば問題ありませんが、マルチバイトにはよわよわです