C言語の標準関数を自作する:文字列処理 strrchr()編
目次
はじめに
C言語には、文字列から指定した文字を検索し、その文字が最後に出現した位置を取得するための標準関数として strrchr() が用意されています。
今回は、この strrchr() を標準Cライブラリの strrchr() を利用せずに自作します。
このプロジェクトでは、標準関数と同じインターフェースを提供するラッパー関数と、実際の処理を行うコア関数を分離する構成を採用しています。
今回作成する strrchr() は、以下のような構成とします。
利用側
↓
my_strrchr()
↓
core_strrchr()
my_strrchr() が利用側から呼び出され、実際の検索処理は core_strrchr() が担当します。
なお、本プロジェクトでは、自作した標準関数模擬関数であるラッパー関数を、他の自作関数の実装から利用することを許容しています。
つまり、標準Cライブラリの関数を直接利用することは禁止しますが、自作した標準関数模擬関数については、必要に応じて他の自作関数から利用することができます。
これは、自作した標準関数群を組み合わせて利用できるようにするためのプロジェクト上の設計方針です。
1. strrchr()の仕様を確認
まず、標準関数としての strrchr() の仕様を確認します。
Linux環境では、以下のコマンドでmanページを確認できます。
man strrchr
オンラインでは、Linux man-pagesの strrchr(3p) — Linux manual page を参照できます。
strrchr(3p) — Linux manual page
1.1 関数プロトタイプ
strrchr() のプロトタイプは以下です。
char *strrchr(const char *s, int c);
strrchr() は、第1引数で指定された文字列から、第2引数で指定された文字が最後に出現する位置を検索し、その位置へのポインタを返します。
文字が見つからない場合は NULL を返します。
第2引数が int になっている点にも注意が必要です。
検索対象の文字は int で指定しますが、実際には文字値として扱われます。
例えば、
char *lp_Result = strrchr("Hello", 'l');
とした場合、"Hello" の中で最後に出現した 'l' へのポインタが返されます。
1.2 最後に出現した文字を検索する
strchr() が文字列の先頭から検索して最初に見つかった文字を返すのに対して、strrchr() は最後に出現した文字を返します。
例えば、
Hello
↑ ↑
l l
という文字列の場合、'l' は2回出現します。
strchr() では最初の 'l' が返されますが、strrchr() では最後の 'l' が返されます。
例えば、
const char *lp_Result = strrchr("Hello", 'l');
の場合、結果は文字列の以下の位置を指します。
Hello
↑
l
返されたポインタから文字列を表示すると、
llo
となります。
これは、返されたポインタが最後の 'l' を指しているためです。
1.3 戻り値
strrchr() は、検索対象の文字が見つかった場合、その文字が最後に出現した位置へのポインタを返します。
文字が見つからなかった場合は NULL を返します。
例えば、
char lv_Buffer[] = "Hello";
char *lp_Result = strrchr(lv_Buffer, 'l');
の場合、
Hello
↑
lp_Result
となります。
lp_Result は、lv_Buffer 内の最後の 'l' を指します。
また、
char *lp_Result = strrchr(lv_Buffer, 'x');
のように、存在しない文字を検索した場合は、
lp_Result == NULL
となります。
1.4 '\0'も検索対象になる
strrchr() では、文字列の終端を表す '\0' も検索対象にできます。
例えば、
char lv_Buffer[] = "Hello";
char *lp_Result = strrchr(lv_Buffer, '\0');
とした場合、文字列の終端位置へのポインタが返されます。
H e l l o \0
↑
lp_Result
"Hello" は5文字なので、
lp_Result - lv_Buffer
は 5 になります。
この点は strchr() と同様です。
検索処理では通常の文字だけでなく、文字列終端の '\0' も検索対象になることに注意する必要があります。
1.5 空文字列の場合
空文字列 "" は、先頭の文字がそのまま '\0' になっています。
例えば、
char lv_Buffer[] = "";
char *lp_Result = strrchr(lv_Buffer, 'A');
の場合、'A' は存在しないため、
lp_Result == NULL
となります。
一方、
char *lp_Result = strrchr(lv_Buffer, '\0');
の場合は、文字列終端である先頭位置へのポインタが返されます。
1.6 NULLポインタについて
標準 strrchr() に NULL ポインタを渡した場合の動作は未定義です。
例えば、
strrchr(NULL, 'A');
のような呼び出しを、標準 strrchr() の正常な仕様として扱うことはできません。
今回の自作関数では、プロジェクト独自の安全性要件として、第1引数が NULL の場合には NULL を返すようにします。
2. 実装
ここまでで、標準 strrchr() の基本的な仕様を確認しました。
では、先程確認した仕様を参考に、実際に strrchr() を自作してみます。
今回のプロジェクトでは、利用側から呼び出されるラッパー関数と、実際の処理を行うコア関数を分離しています。
実装はこちらです。
2.1 コア関数のヘッダーファイル
まず、コア関数のインターフェースをヘッダーファイルに定義します。
#ifndef CORE_STRING_CORE_STRRCHR_H
#define CORE_STRING_CORE_STRRCHR_H
#include "myc/myc_define.h"
EXTERN char *core_strrchr(const char *ap_Source, int av_Character);
#endif
第1引数は検索対象の文字列なので const char * とします。
第2引数は検索対象の文字を指定するため、標準 strrchr() と同様に int とします。
戻り値は検索結果を指すポインタであり、標準 strrchr() と同じ char * とします。
2.2 コア関数の実装
コア関数では、実際の文字検索を行います。
#include <stddef.h>
#include "core/string/core_strrchr.h"
char *core_strrchr(const char *ap_Source, int av_Character)
{
size_t lv_Index = 0;
char *lp_Last = NULL;
if (NULL == ap_Source) {
return NULL;
}
while (1) {
if (av_Character == ap_Source[lv_Index]) {
lp_Last = (char *)&ap_Source[lv_Index];
}
if ('\0' == ap_Source[lv_Index]) {
break;
}
lv_Index++;
}
return lp_Last;
}
この実装では、検索対象の文字を見つけるたびに lp_Last を更新します。
そのため、文字列の最後まで検索した後には、lp_Last が最後に出現した文字を指しています。
また、'\0' と検索対象が一致した場合も、先に lp_Last を更新してから検索を終了するため、
core_strrchr("Hello", '\0');
では文字列終端へのポインタが返されます。
ap_Source は const char * として受け取っているため、検索処理中に文字列の内容を書き換えることはありません。
(char *) による変換は、標準 strrchr() の戻り値型である char * を実現するために行っています。
2.3 ラッパー関数のヘッダーファイル
次に、利用側から呼び出されるラッパー関数のインターフェースを定義します。
#ifndef WRAPPER_STRING_MY_STRRCHR_H
#define WRAPPER_STRING_MY_STRRCHR_H
#include "myc/myc_define.h"
/*
* 標準関数: strrchr.
*
* 文字列から指定した文字を検索し、最後に出現した位置を返します.
*
* 引数:
* ap_Source : 検索対象の文字列.
* av_Character : 検索対象の文字.
*
* 戻り値:
* 文字発見 : 最後に出現した位置へのポインタ.
* 文字未発見 : NULL.
* NULL入力 : NULL.
*
* 標準strrchrとの相違点:
* 標準strrchrではNULLポインタを渡した場合の動作は未定義です.
* 本関数ではNULLを独自に処理し、NULLを返します.
*/
EXTERN char *my_strrchr(const char *ap_Source, int av_Character);
#endif
こちらのヘッダーファイルにもインクルードガードを付けています。
また、利用側に対して標準 strrchr() と同じ引数の型を提供します。
2.4 ラッパー関数の実装
ラッパー関数では、利用側から呼び出される my_strrchr() を実装します。
#include "core/string/core_strrchr.h"
#include "wrapper/string/my_strrchr.h"
char *my_strrchr(const char *ap_Source, int av_Character)
{
return core_strrchr(ap_Source, av_Character);
}
ラッパー自身は検索処理を行わず、コア関数へ処理を委譲しています。
このように、今回の実装では、
my_strrchr()
↓
core_strrchr()
↓
文字列を末尾まで検索
という役割分担になっています。
ここまでで、実際に動作する my_strrchr() の実装を確認しました。
ただし、このコードには、本プロジェクト独自の仕様やコーディング規約が反映されています。
🔗 関連記事・関連リンク
この記事の最新アップデートや、このシリーズの関連記事は以下のリンクからご覧いただけます。
内容は基本的に同じですので、お好みのサイトでお読みください。
🟢 Zenn
-
この記事の最新版はこちら。
-
「標準関数自作」Topicの関連記事はこちら。
🔵 Qiita
-
「標準関数自作」タグの関連記事はこちら。
🧡 note
-
このシリーズの記事はこちら。
-
要件定義以降は、有料パートとしてこちらで公開。