自由拡張
自由に足す前に、形を決める
ここまでで成績管理CLI は一通り動くようになりました。ここからが本当の意味での制作です。使っていて足りないと思った機能を、自分で足します。
ただし「好きに作ってよい」と言われて手が止まる人は多いはずです。止まる理由ははっきりしていて、どこまでできれば完成なのかが決まっていない からです。プログラムを書く前に決めることは 3 つあります。何を入力するか、何を出力するか、うまくいかなかったときどうするか。この 3 つが決まれば、あとは書くだけの作業になります。
今回は、その決め方の練習として 2 つの機能を仕様つきで用意しました。検索と順位表です。仕様が先にあるので、動いたかどうかを自分で判定できます。
検索は先頭から順に比べる
名前で 1 人を探す処理は、配列を先頭から見て、名前が一致したところで止めるだけです。文字列の比較は第6章でやった strcmp を使います。
if (strcmp(list[i].name, key) == 0) {
/* 見つかった */
}== で比べてはいけません。list[i].name は配列の先頭アドレスなので、== だと中身ではなくアドレスの比較になり、同じ文字列でも一致しません。見つからずに最後まで進んだ場合の表示を必ず用意します。これが「うまくいかなかったときどうするか」にあたります。
並べ替えは元のデータを壊さない
点数の高い順に出すには、隣どうしを比べて大きいほうを前に送る操作をくり返します。よく バブルソート と呼ばれる方法で、二重ループで書けます。第4章の九九と同じ形です。
気をつけるのは、元の配列をそのまま並べ替えてしまうと登録順が失われることです。一覧表示は登録順のままであってほしいので、作業用の配列にコピーしてから並べ替えます。構造体は work[i] = list[i] と書くだけでメンバごと丸ごとコピーされます。
同点のときにどちらが上かは仕様で決めておきます。今回は入れ替えの条件を「後ろのほうが厳密に大きいとき」だけにするので、同点なら登録順のままになります。
機能を足すと、前の機能が壊れることがある
新しい処理を足したあとは、必ず前からある処理も動かして確かめます。今回でいえば、順位表を出したあとに一覧を出して、登録順が保たれているかを見ます。並べ替えで元の配列を触ってしまっていると、ここで初めて気づけます。
こういう「前は動いていたのに、直したら動かなくなった」現象は デグレード と呼ばれ、実務でいちばん嫌われる不具合です。防ぐ方法は単純で、確認したい入力と期待する出力の組をあらかじめ書いておき、変更のたびに全部流すことです。この課題のテストケースがまさにその形になっています。
この先は自分の仕様で
課題としては検索と順位表を作りますが、自分の手元ではここで止める必要はありません。合格者だけを数える、ランクごとの人数を出す、保存したファイルから起動時に読み戻す。どれも第8章までの知識だけで書けます。足すときは今回と同じで、入力と出力と失敗時の動きを先に紙に書いてから始めてください。
要件
- 1 から 4 と 0 の動きは前回と同じ。starterCode に入っているので変えない
- 5 を選んだら続けて名前を1つ読み、一致する学生を「田中 80点」の形で出す
- 一致する学生がいなければ「見つかりません」と出す
- 6 を選んだら点数の高い順に「1位 鈴木 95点」の形で全員を出す。0人なら「学生がいません」
- 同点の場合は登録が早いほうを上にする
- 6 のあとで 2 の一覧を出しても、並びは登録順のままにする
- 5 と 6 と 0 から 4 以外の番号は「その番号はありません」と出す
- 入力をうながす案内文は一切出さない
入出力例
main("1
田中
80
1
鈴木
95
1
佐藤
70
5
鈴木
5
山田
6
0
") → "田中を登録しました
鈴木を登録しました
佐藤を登録しました
鈴木 95点
見つかりません
1位 鈴木 95点
2位 田中 80点
3位 佐藤 70点
終了します"
main("6
5
田中
1
田中
60
6
0
") → "学生がいません
見つかりません
田中を登録しました
1位 田中 60点
終了します"
main("1
田中
80
1
鈴木
80
1
佐藤
90
6
2
7
0
") → "田中を登録しました
鈴木を登録しました
佐藤を登録しました
1位 佐藤 90点
2位 田中 80点
3位 鈴木 80点
田中 80点
鈴木 80点
佐藤 90点
その番号はありません
終了します"