構造体と関数
1人分をそのまま引数にできる
表示処理を関数に切り出すとき、以前なら名前と点数を別々の引数で渡す必要がありました。構造体なら 1 個で済みます。
void print_student(struct Student s) {
printf("%s %d点\n", s.name, s.score);
}呼ぶときは print_student(first); です。引数の型が struct Student なので、名前も点数も一緒に届きます。あとから「学年」のメンバを増やしても、関数の引数リストを書き換えずに済みます。
渡るのはコピー
ここで前章のポインタの話が効いてきます。関数の引数は代入と同じで 値のコピー が渡ります。構造体も例外ではなく、中身がまるごと複製されて関数に入ります。
つまり、関数の中で s.score = s.score + 5; と書いても、それは手元のコピーを変えただけです。呼び出し元の変数は 1 点も動きません。表示するだけの関数ならコピーで困りませんが、書き換えたいときはコピーでは届かない のです。
書き換えたいならアドレスを渡す
第7章の swap と同じ解き方をします。値ではなく アドレス を渡します。
void add_bonus(struct Student *p, int bonus) {
(*p).score = (*p).score + bonus;
}struct Student *p は「構造体のある住所を入れる変数」です。*p でその住所にある本体を取り出し、さらに .score でメンバを指します。* を先に効かせるためのカッコが要る点に注意してください。カッコを外した *p.score は意味が変わってしまいます。
この (*p).score は使う頻度が高いわりに書きにくいので、C には専用の省略記法があります。
p->score = p->score + bonus;-> は「アドレスの先にあるメンバ」を意味する矢印です。(*p).score と完全に同じ意味で、読みやすいこちらを使うのが普通です。ポインタ変数なら ->、実体の変数なら .、という使い分けだけ覚えておけば当面は足ります。
呼び出し側は add_bonus(&s, 5); と、& を付けて住所を渡します。scanf に & を付けていたのと同じ理屈です。
どちらで受け取るか
判断はごく単純です。中身を書き換えたいならポインタ、表示や計算をするだけなら値で受け取ります。実務ではもうひとつ、構造体が大きいときはコピーの手間を惜しんで、書き換えないのにポインタで受け取ることもあります。今のところ struct Student は小さいので、値で渡して困ることはありません。
なお、ここで扱うポインタは「既にある変数の住所」だけです。メモリを新しく確保する malloc はこのコースでは使いません。手元の変数に & を付けて渡す、という形さえ守れば、住所が無効になる心配もありません。
では、表示する関数と点数を書き換える関数の両方を作ってみましょう。
要件
- print_student は struct Student を値で受け取り「田中 82点」の形で表示する
- add_bonus は struct Student のポインタと加点する点数を受け取り、点数を書き換える
- add_bonus の中では -> を使ってメンバを書き換える
- 加点前と加点後の2行を print_student で表示する
入出力例
main("田中 82", "5") → "田中 82点
田中 87点"
main("佐藤 90", "8") → "佐藤 90点
佐藤 98点"