構造体と関数

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 はこのコースでは使いません。手元の変数に & を付けて渡す、という形さえ守れば、住所が無効になる心配もありません。

では、表示する関数と点数を書き換える関数の両方を作ってみましょう。

要件

  1. print_student は struct Student を値で受け取り「田中 82点」の形で表示する
  2. add_bonus は struct Student のポインタと加点する点数を受け取り、点数を書き換える
  3. add_bonus の中では -> を使ってメンバを書き換える
  4. 加点前と加点後の2行を print_student で表示する

入出力例

main("田中 82", "5") → "田中 82田中 87点" main("佐藤 90", "8") → "佐藤 90佐藤 98点"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.c
学習モード

メモ

構造体と関数

⌘S で保存