はじめての 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プログラミング」