メニュー統合
8章ぶんの部品を1本につなぐ
前回まででバラバラに書いてきた部品が、これで揃いました。第6章の配列、第8章の構造体、第5章の関数、第4章のループ、第3章の分岐、そして第8章のファイル保存です。今回はそれらを 1 つの main の下にまとめて、番号を打つと処理が選べる メニュー方式 の成績管理CLIにします。
メニュー方式の骨は、第4章でやった「条件を満たす間くり返す」だけです。番号を 1 つ読む、その番号で処理を選ぶ、また番号を読む。終了番号が来たらループを抜ける。それだけです。
while (scanf("%d", &command) == 1 && command != 0) {
if (command == 1) {
/* 登録 */
} else if (command == 2) {
/* 一覧 */
}
}scanf の戻り値は 読み取れた項目の数 です。入力が尽きると 1 より小さい値が返るので、== 1 を条件に入れておくと、入力が終わったときも無限ループにならずに止まります。
処理は関数に、データは構造体配列に
if の中に処理を直接書いていくと、main があっという間に読めない長さになります。第5章でやったとおり、まとまった仕事は関数に切り出します。一覧表示なら show_all、集計なら show_summary、保存なら save_all という具合です。こうすると main は「番号を読んで、対応する関数を呼ぶだけ」になり、全体の流れが数行で見渡せます。
渡すデータは Student の配列と、いま何人入っているかを表す count の 2 つです。配列は関数に渡すと先頭アドレスが渡るので、関数側で書き換えた内容は呼び出し元にも残ります。第7章で確かめた性質がそのまま効いています。
画面に出す文字は仕様として決める
このコースの採点は、標準入力を流して標準出力を突き合わせる形です。そのため 入力をうながす案内文は出しません。番号や名前を打ってくださいという文言を出すと、それも出力の一部として比較されてしまうからです。
実務では案内文を出したほうが親切ですが、自動テストにかける部分と、人に見せる部分を分けて考えるのは実務でも同じです。まずは出力仕様を厳密に決め、そのとおりに動くことを機械で確かめる。案内文は最後に printf を足せば済みます。
一気に完成させようとしない
まとめる作業でつまずく人の多くは、全部書いてから一度にコンパイルします。そうすると 20 個のエラーが同時に出て、どれが本当の原因か分からなくなります。
順番はこうです。まず番号を読んで終了だけできるループを完成させ、動かします。次に登録を足して動かします。次に一覧、次に集計、最後に保存です。1 つ足すごとにコンパイルして走らせれば、エラーが出ても原因はいま書いた数行の中にしかありません。第1章でコンパイルエラーの読み方をやったのは、この場面のためでもあります。
小さく動かして少しずつ育てるやり方は、このあと何を書くときにも効きます。では、成績管理CLI を完成させましょう。
要件
- Student 構造体 (char name[32] と int score) と、その配列で最大10人まで持つ
- 番号を1つ読み、1 なら登録、2 なら一覧、3 なら集計、4 なら保存、0 なら終了する
- 1 は続けて名前と点数を読み、「田中を登録しました」の形で出す
- 2 は登録順に「田中 80点」の形で1行ずつ出す。0人なら「学生がいません」
- 3 は「人数は3人です」「合計は245点です」「平均は81.7点です」の3行を出す。平均は %.1f。0人なら「学生がいません」
- 4 は scores.txt に書き出し、「3件保存しました」と出す
- 1〜4 と 0 以外の番号は「その番号はありません」と出す
- ループを抜けたら最後に「終了します」と出す
- 入力をうながす案内文は一切出さない
入出力例
main("1
田中
80
1
鈴木
70
1
佐藤
95
2
3
4
0
") → "田中を登録しました
鈴木を登録しました
佐藤を登録しました
田中 80点
鈴木 70点
佐藤 95点
人数は3人です
合計は245点です
平均は81.7点です
3件保存しました
終了します"
main("2
3
9
0
") → "学生がいません
学生がいません
その番号はありません
終了します"
main("1
田中
70
1
鈴木
75
3
0
") → "田中を登録しました
鈴木を登録しました
人数は2人です
合計は145点です
平均は72.5点です
終了します"