はじめての C
C programming note*1
プログラムの 扱う内容が 少し 複雑に なってきたときは、
内部の しかけを 考える前に、まず 外部の仕様を 考えよう。 プログラムと その外側との やりとりのことだ。
この場合の 外部 (外側) とは 「人間が そのプログラムを どう操作し どんな形式で 入力を 与え、 そして どんな出力を 得るか」 と いうことだ。
そこで 以下のような 外部仕様 (インターフェイス仕様) を 考える。
1. find1.c を 改良し、複数の文字列を 探せるようにする。
2. 検索文字列リストは、別のテキストファイル (を つくり、そこ) に 1行あたり 1つの 文字列として 格納されているものとする。
3. 検索文字列リストの ファイル名は -f filename の オプションで コマンドラインで 指定できるようにする。
4. その場合の 入力元の指定は find1.c と 同様とする。
5. -f オプションの 指定が なかった場合は、find1.c と 同様の処理を するものとする (入力元の 指定についても)。
6. 検索文字列リストのファイルに 指定できる 検索語は 最大 256個 以内とする (それと、検索語の サイズも きめておく)。
7. ある 1つの行が 複数文字列に マッチしても、1度しか 出力しない (ようにする)。(p168-169)
1 は 当初の目的、3 から 7 までが いわゆる ユーザインターフェイスと よばれるものです。
つまり find1 と 同じく
$ ./find2 keyword file1 file2
と 実行しても、あるいは$ ./find2 -f search_list file1 file2
のように リストで 指定しても、どちらも 検索が 可能だ ということです。まず 最初に、関数 find() を 「複数の 文字列との 照合」が できるよう 変更を 加えます。
void find(FILE *fp, char *word[], int n)
{
char buf[MAX_SIZE];
int i; while (fgets(buf, MAX_SIZE, fp) != NULL) {
for (i = 0; i < n; i++) {
if (strstr(buf, word[i])) {
fputs(buf, stdout);
break;
}
}
}
}
仮引数の ところで、変数 word が 文字列へのポインタから 配列へのポインタに 変更され、また 検索文字列の数 n が 追加されています。
n は 比較すべき 文字列の個数だ。
文字列を 1つだけ 指定する やりかた (find1 と 同じ形) で 起動したときには、この n の値は 1 に なっている。
検索文字列用のファイルの中に 3つの文字列が 格納されていたのならば、n は 3 だ。
for文による くり返しを 使って 文字列の 個数分だけ 比較を 行なっている。
検索文字列は この word という名前の 配列に 入っていて、word[0] には 1つめの 検索文字列、word[1] には 2つめの 検索文字列 ... のように 文字列が 格納されているので、この if文で 比較が できるわけだ。(p174)
find() 関数に 渡される 各引数が どのように 準備されるのかは ちょっと置いといて、次は ユーザインターフェイスの 7つめにあった しくみについて、
fputs() したあとの break に 着目してほしい。
この break は ループからの 脱出のためのものだ。 break文を 実行すると、for や while などの くり返しループを 脱出し、ループの 後ろの部分へ 分岐する。
(関数 find() の) この部分では、while ループの中に for ループが 入っているという 二重のループが 構成されている。
break文は 1つのループしか 脱出しないので、この場合は 内側の for ループから 脱出して for の 終わったところに 分岐するわけだ。
この break にて、配列 word に 入っている 文字列のうち いずれか 1つに マッチしたならば、すぐに その行を 出力して、次の行の 照合に 制御を 移動させている。
すなわち、「同じ行に 探している文字列が 2つ以上 存在しても、出力は 1回だけ」 を 実現している (ことになる)。(p174-175)
*1:「作ってわかる Cプログラミング」