このページでは、C言語の標準ライブラリ関数である strchr と strrchr について解説していきます。
これらの関数を利用することで、”パスからファイル名のみを取得する” 等を簡単に実現することができるようになります。

Contents
strchr と strrchr 関数
strchr と strrchr は両方とも文字列の中から “特定の文字” を探索する関数になります。

引数
strchr と strrchr の引数は共通で、第1引数に文字列のアドレス s を指定し、第2引数に文字 c を指定します。第2引数の型は int ですが、内部では char 型として扱われるようです。
#include <string.h>
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
第1引数の s には const 修飾子が指定されているため、第1引数 s に指定したアドレスの文字列が、strchr や strrchr の実行によって変更されるようなことはありません。
スポンサーリンク
返却値
上記のように引数を指定して strchr や strrchr を実行すれば、s の指す文字列の中に文字 c が存在する場合、これらの関数からはその文字 c が存在する位置のアドレスが返却されます。存在しない場合は NULL が返却されます。返却されるのが文字のアドレスであるため、返却値の型は char * となります。
そして、s の指す文字列の中に文字 c が存在する場合は strchr と strrchr とで返却されるアドレスが異なる場合があります。その場合とは、s の指す文字列の中に文字 c が複数個存在する場合です。複数存在する場合、strchr は複数個存在する文字 c の内、一番 “前側” に存在する文字 c の位置のアドレスを返却します。それに対し、strrchr は複数個存在する文字 c の内、一番 “後ろ側” に存在する文字 c の位置のアドレスを返却します。

使用時の注意点
C言語においては、文字列は必ずヌル文字('\0')で終端されている必要があります。s の指す文字列がヌル文字で終端されていない場合、返却値として得られる値が期待するものではないものになる可能性があるので注意してください。
ヌル文字に関しては下記ページで解説していますので、詳しく知りたい方は別途下記ページを参照していただければと思います。
また、strchr や strrchr を使用するためには string.h をインクルードしておく必要がありますので、この点も忘れないように注意してください。以降で紹介するソースコードでは基本的に strchr や strrchr を使用する箇所のみを示していくため string.h のインクルード部分は含まれません。実際にコンパイルして実行するためには別途 string.h をインクルードする必要があるので注意してください。
strchr と strrchr 関数の利用例
ここまで strchr や strrchr について解説していきましたが、文章で見ると難しく感じるかもしれないので、実際に strchr や strrchr を利用するソースコードや図を使って strchr や strrchr について学んでいきましょう!
スポンサーリンク
strchr や strrchr で文字の存在を確認する
まず、strchr や strrchr で “文字列の中に特定の文字が含まれるかどうか” について判断を行う例について紹介していきます。
下記は、アドレス s の文字列("abcdabcdabcd")に 'b' の文字が存在するかどうかを strchr を利用して確認する例となります。
char *s = "abcdabcdabcd";
char c = 'b';
char *ret;
ret = strchr(s, c);
if (ret != NULL) {
printf("文字列sの中に%cは存在します!!!\n", c);
} else {
printf("文字列sの中に%cは存在しません...\n", c);
}
下記は、アドレス s の文字列("abcdabcdabcd")に 'b' の文字が存在するかどうかを strrchr を利用して確認する例となります。
char *s = "abcdabcdabcd";
char c = 'b';
char *ret;
ret = strrchr(s, c);
if (ret != NULL) {
printf("文字列sの中に%cは存在します!!!\n", c);
} else {
printf("文字列sの中に%cは存在しません...\n", c);
}
strchr の場合も strrchr の場合も、引数 s の指す文字列の中に引数 c の文字が存在しない場合は NULL を、それ以外の場合は、その文字の位置のアドレスを返却する関数となります。したがって、引数 s の指す文字列の中に引数 c の文字が存在するか否かの判断は、上記のように関数の返却値が NULL であるかどうかを確認することで実現可能です。

そして、この場合は、返却されるアドレスに関わらず、関数の返却値が NULL であるかどうかしか確認しないため、strchr と strrchr のどちらを利用しても結果は変わらないことになります。つまり、単に文字列の中に特定の文字が存在するかどうかを判断したいのであれば、どちらの関数を利用してもオーケーです。ただし、上の図で示すように、実際には返却されるアドレスが異なります。
また、上記のソースコードにおいて、c に代入する文字を 'a'・'b'・'c'・'d' 以外のものにしてみれば、strchr や strrchr が NULL を返却するようになることも確認できると思います。
strchr や strrchr で文字の位置を取得する
先ほどは、単に文字列の中に特定の文字が存在するかどうかを確認する例であったため、strchr と strrchr のどちらを利用しても結果は同じとなりました。ここからは、strchr と strrchr のどちらを利用するかで結果が変化する例を示していきます。
まずは、strchr や strrchr で、”特定の文字が存在する「位置」を取得する例” を示したいと思います。より具体的には、文字列の先頭の文字を1文字目とした場合に、特定の文字が何文字目に存在するのかを出力する例となります。
strchr で文字の位置を取得する
下記が strchr で特定の文字の位置を取得する例となります。
char *s = "abcdabcdabcd";
char c = 'b';
char *ret;
ret = strchr(s, c);
if (ret != NULL) {
printf("%cは%ld文字目に存在します!!!\n", c, ret - s + 1);
} else {
printf("文字列sの中に%cは存在しません...\n", c);
}
この場合の実行結果は下記となります。確かに文字 'b' は s の指す文字列の2文字目に存在しているため、正しい結果が得られていることになります。
bは2文字目に存在します!!!
上記で文字の位置が取得できる理由を説明しておきます。
まず文字列 "abcdabcdabcd" とポインタ s の関係を示すと下図のようになります。重要なのは s が文字列の先頭を指しているという点になります。つまり、s は文字列の先頭の文字を指しています。ここでは文字列の先頭の文字を1文字目と考えるようにしているため、s は文字列の1文字目を指していることになります。

さらに、strchr の返却値 ret には、"abcdabcdabcd" の中の文字 'b' のアドレスが格納されることになります。strchr では、文字列の中に複数の文字 'b' が存在する場合、一番前側の位置のアドレスが格納されることになります。つまり、 ret (strchr の返却値) は下図のような位置を指していることになります。

そして、これらの ret と s は文字を指すポインタ変数(char * 型のポインタ変数)であり、ret - s により ret と s とが何文字分離れているのかを算出することができます。
もう少し正確にいうと、char * 型のポインタ変数同士の引き算では、それらが char 型の変数としていくつ分離れているかを算出することができます
今は文字を char 型として扱っているため、何文字分離れているかを算出することができることになります
したがって、ret - s により、ret の指す文字が先頭の文字(s の指す文字)から何文字分離れているかを算出することができ、ここでは先頭の文字の位置を 1 文字目と考えているため、結局 ret の指す位置の文字が何文字目であるかは ret - s + 1 により求めることができることになります。
このような考え方で第2引数で指定された文字の位置を取得できるよう、下記部分で ret - s + 1 を行っています。ちなみに、先頭の文字を第 0 文字目として考えるのであれば +1 は不要になります。
printf("%cは%ld文字目に存在します!!!\n", c, ret - s + 1);
上記の場合は strchr は s + 1 の値を返却することになりますので ret - s の結果は 1 となります。さらに、文字列の先頭を 1 文字目としていますので、その 1 をこの結果に足し合わせてやれば、strchr の第2引数で指定した文字の位置として 2 が得られることになります。
strrchr で文字の位置を取得する
今度は strrchr を利用して同様のことを行なってみましょう!
下記は strrchr で特定の文字の位置を取得する例となります。strchr で文字の位置を取得する で示したソースコードから strchr 部分を strrchr に置き換えただけのものになります。
char *s = "abcdabcdabcd";
char c = 'b';
char *ret;
ret = strrchr(s, c);
if (ret != NULL) {
printf("%cは%ld文字目に存在します!!!\n", c, ret - s + 1);
} else {
printf("文字列sの中に%cは存在しません...\n", c);
}
この場合の実行結果は下記となります。確かに文字 'b' は s の指す文字列の10文字目に存在しているため、正しい結果が得られていることになります。
bは10文字目に存在します!!!
ポイントは、strchr を利用した時とは結果が異なる点になります。文字の位置自体は strchr で文字の位置を取得する で示したものと同じ考え方で求めています。
ですが、上記の例の場合、strchr と strrchr とでは返却するアドレスが異なるため、求められる文字の位置も異なる結果となります。
前述の通り、strchr は一番 “前側” に存在する文字 c の位置のアドレスを返却します。それに対し、strrchr は一番 “後ろ側” に存在する文字 c の位置のアドレスを返却します。このような違いがあるため、第2引数に指定した文字 c が第1引数に指定したアドレスの文字列に複数存在する場合は、strchr と strrchr とで返却値が異なることになります。
具体的には、今回の場合は下図のように返却されるアドレスが異なることになります。

このように、strchr と strrchr とでは返却するアドレスが異なるため、当然 ret - s の値も異なることになります。今回の例の場合は ret - s が 9 となるため、結果的に 'b' が 10 文字目に存在する旨が printf 関数で出力されることになります。
こんな感じで、strchr と strrchr は返却するアドレスが異なるため、目的に合わせて適切に使い分ける必要があります。
strrchr で文字列を分離する
最後に、strrchr を利用した文字列の分離について解説していきます。
今回は、下記のようなディレクトリ間が / で区切られるファイルパスを、ファイルの存在するディレクトリのパス(以降、「ディレクトリパス」と略します)と「ファイル名」に分離する例を示していきたいと思います。
/Users/Daeu/Documents/test.txt
要は、上記のようなファイルパスが入力された際に次のようなディレクトリパスのみとファイル名のみを取得する例となります。
- ディレクトリパス:
/Users/Daeu/Documents - ファイル名:
test.txt
ディレクトリパスとファイル名に分離する
最初にディレクトリパスとファイル名に分離するための考え方について説明しておきます。
まず、ディレクトリ間が / で区切られるファイルパスは、ファイルの存在するディレクトリパスと、そのファイル名から下記のように構成されます。
- ディレクトリパス;最後の
/より前側 - ファイル名;最後の
/より後ろ側
そして、この 最後の / の位置のアドレスは、第1引数にファイルパスの文字列、第2引数に '/' を指定して strrchr を実行することで取得可能です。
この時の strrchr の返却値を ret、さらに第1引数に指定した文字列、すなわちファイルパスを path とすれば、ret + 1 が “ファイル名” の文字列の先頭のアドレスとなります(スペースの都合により、図ではファイルパスを短縮しています)。

さらに、ret の位置の文字をヌル文字('\0')で上書きしてやれば、path はファイルパスではなくディレクトリパスの文字列となります。なぜなら、ヌル文字は文字列の終端を表す文字であり、最後の / をヌル文字にしてやれば、path はそれよりも前の部分のみの文字列とみなされるようになるからになります。

このような考え方で実装すれば、ファイルパスをディレクトリパスとファイル名に分離することが可能です。下記は、その実例となります。
char path[] = "/User/Daeu/Documents/text.txt";
char c = '/';
char *ret, *dir_path, *file_name;
ret = strrchr(path, c);
if (ret != NULL) {
path[ret - path] = '\0';
dir_path = path;
file_name = ret + 1;
printf("dir_path : %s\n", dir_path);
printf("file_name : %s\n", file_name);
} else {
printf("文字列sの中に%cが存在しません...\n", c);
}
Windows の場合など、ファイルパスをディレクトリパスとファイル名に分離するような関数が既に用意されている環境もありますが、strrchr を利用すれば自作でも簡単にこのような機能を実現することが可能です。
ここでは、文字列の中の “特定の文字の最後の位置” に注目をしたため strrchr を利用しましたが、特定の文字の最初の位置に注目する場合は strchr を使って文字列の分離を行うことになると思います。
ここまで説明してきたように strchr や strrchr は文字列の中に特定の文字が含まれているかどうかを確認するだけでなく、様々なことに活用可能ですので、是非これらの関数についても覚えておきましょう!
スポンサーリンク
まとめ
このページでは、C言語の標準ライブラリ関数である strchr と strrchr について解説しました!
strchr と strrchr は、文字列の中から特定の文字を探索する関数になります。文字列の中に特定の文字が存在しない場合、strchr と strrchr は NULL を返却しますし、存在する場合は、その文字の位置のアドレスを返却します。そのため、strchr と strrchr の返却値が NULL か否かを判断することで、その文字が文字列の中に存在するかどうかを確認することができます。
また、特定の文字が文字列の中に複数個存在する場合、strchr は一番 “前側” に存在する文字 c の位置のアドレスを返却します。それに対し、strrchr は一番 “後ろ側” に存在する文字 c の位置のアドレスを返却します。このように、strchr と strrchr とでは返却するアドレスが異なることがあるため、自身が行いたい処理や実装したい機能に合わせて適切に使い分けることが重要となります。
今回は、文字列の中から特定の文字を探索する関数を紹介しましたが、文字列の中から “特定の文字列” を探索する strstr 関数も存在します。この strstr 関数に関しては下記ページで紹介していますので、是非こちらのページも読んでみていただければと思います!

