リスト版を完成させる
リスト版を1本にまとめる
第5章で先頭挿入・走査・任意位置の削除・全解放を、第6章で引数とバイナリ保存を書いてきました。部品はもう揃っています。今回はそれらを 1 つの main の下にまとめて、番号でメニューを選ぶ成績管理CLI のリスト版を完成させます。入門で作った配列版と同じ操作ができて、中身だけが連結リストに入れ替わっている状態が目標です。
配列版との違いは 2 つあります。ひとつは上限が無いこと、もうひとつはデータの持ち主がヒープに移ったことです。配列版は Student list[10] と書いた時点で 10 人が上限で、そのメモリは main が生きている間ずっと確保されたままでした。リスト版は登録するたびに malloc でノードを 1 つ借り、削除するたびに返します。何人でも入る代わりに、借りたものを返す責任がこちらに移ります。
head を返す形でそろえる
リストを触る関数の書き方は、head が変わりうるかどうか で決まります。先頭を削除したときや、空のリストに最初の 1 人を足したときは、head そのものが別のノードを指すことになります。関数の中で仮引数に代入しても呼び出し元の変数は変わらないので、新しい head を戻り値で返して head = delete_by_name(head, name); のように受け直します。
ポインタのポインタを渡す書き方もありますが、戻り値で返すほうが読む人にとって分かりやすい利点があります。呼び出し側の 1 行を見ただけで、この関数は head を付け替えることがあると読み取れるからです。
確保が2回なら解放も2回
今回のノードは名前を char *name で持ちます。第3章でやったとおり、名前は入力された長さに合わせて確保します。つまりノードを 1 つ作るたびに、ノード本体と名前の文字列で 確保が 2 回 起きています。ということは、ノードを 1 つ捨てるときも解放は 2 回必要です。free(cur) だけを書くと、名前のぶんが誰からも参照されないままヒープに残ります。
順序も決まっています。free(cur) を先に書いてしまうと、そのあと cur->name を読む手段が消えます。中の名前を先に解放してから、器であるノードを解放します。
メニューは番号を読むだけ
メニューの骨は入門で書いたものと同じです。番号を 1 つ読み、その番号で処理を選び、また番号を読む。終了番号が来たらループを抜けます。scanf の戻り値は読み取れた項目の数なので、scanf("%d", &command) == 1 を条件に入れておくと、入力が尽きたときも無限ループになりません。中身がリストに変わっても、この枠は一切変わらないところが面白い点です。変わったのは、それぞれの分岐が呼ぶ関数の中だけです。
終わり方まで仕様に入れる
このプログラムは 0 が入力されたときと、入力が尽きたときの両方で終わります。どちらの終わり方でも、残っているノードを全部解放してから終わるようにします。今回は解放した件数を最後に表示する仕様にしました。登録した数と解放した数が合っているかを目で確かめられるので、リークしていないことがそのまま出力に現れます。
要件
- ノードは char *name と int score と struct Node *next を持つ。starterCode の定義を変えない
- 番号を1つ読み、1 なら登録、2 なら一覧、3 なら集計、4 なら削除、0 なら終了する
- 1 は続けて名前と点数を読み、末尾に追加して「田中を登録しました」と出す
- 2 は登録順に「田中 80点」の形で1行ずつ出す。0人なら「学生がいません」
- 3 は「人数は3人です」「合計は245点です」「平均は81.7点です」の3行を出す。平均は %.1f。0人なら「学生がいません」
- 4 は続けて名前を読み、一致するノードを外して「田中を削除しました」と出す。無ければ「田中は見つかりません」
- 1 から 4 と 0 以外の番号は「その番号はありません」と出す
- ループを抜けたら残ったノードを全部解放し、「2件解放しました」「終了します」の2行を出す
- 確保した領域は名前ぶんも含めてすべて解放する。入力をうながす案内文は一切出さない
入出力例
main("1 田中 80
1 鈴木 95
1 佐藤 70
2
3
4 田中
2
3
0
") → "田中を登録しました
鈴木を登録しました
佐藤を登録しました
田中 80点
鈴木 95点
佐藤 70点
人数は3人です
合計は245点です
平均は81.7点です
田中を削除しました
鈴木 95点
佐藤 70点
人数は2人です
合計は165点です
平均は82.5点です
2件解放しました
終了します"
main("2
3
4 田中
9
1 高橋 60
2
0
") → "学生がいません
学生がいません
田中は見つかりません
その番号はありません
高橋を登録しました
高橋 60点
1件解放しました
終了します"