/*01*/ #include <stdio.h>
/*02*/ #include <string.h>
/*03*/
/*04*/ struct score{
/*05*/ char name[16];
/*06*/ int math;
/*07*/ int english;
/*08*/ int kei;
/*09*/ };
/*10*/
/*11*/ main()
/*12*/ {
/*13*/ struct score a1, a2;
/*14*/
/*15*/ strcpy(a1.name, "totobon");
/*16*/ a1.math = 90;
/*17*/ a1.english = 70;
/*18*/ a1.kei = 160;
/*19*/
/*20*/ a2 = a1;
/*21*/
/*22*/ printf("a1: name -> %s, math -> %d, english -> %d, kei -> %d\n",
/*23*/ a1.name, a1.math, a1.english, a1.kei);
/*24*/
/*25*/ printf("a2: name -> %s, math -> %d, english -> %d, kei -> %d\n",
/*26*/ a2.name, a2.math, a2.english, a2.kei);
/*27*/
/*28*/ return 0;
/*29*/ }
構造体に対して行える演算は「コピー」「& でアドレスを求める」「メンバにアクセスする」の三つだけです。後ろの二つについては前章でやったので詳しいことは省略します。そこで、この節ではまずコピーについて解説します。
といっても、構造体変数に代入演算「=」を使うとすべてのメンバがそのままコピーされるだけです。ですが、これ以外の演算はできません。構造体変数に整数や別の構造体変数を「+」しても、各メンバのが足されたりはしません(エラー)し、比較演算も使えません。
さて、これまでは score 構造体変数のメンバ kei (合計点)を自分で計算してきました。しかし、これではせっかくプログラミングをしているのに意味がありません。ではどうしましょう?もちろん
a1.kei = a1.math + a1.english;
/*01*/ #include <stdio.h>
/*02*/ #include <string.h>
/*03*/
/*04*/ struct score{
/*05*/ char name[16];
/*06*/ int math;
/*07*/ int english;
/*08*/ int kei;
/*09*/ };
/*10*/
/*11*/ main()
/*12*/ {
/*13*/ struct score Kei(struct score dat);
/*14*/ struct score a1;
/*15*/
/*16*/ strcpy(a1.name, "totobon");
/*17*/ a1.math = 90;
/*18*/ a1.english = 70;
/*19*/
/*20*/ a1 = Kei(a1);
/*21*/
/*22*/ printf("a1: name -> %s, math -> %d, english -> %d, kei -> %d\n",
/*23*/ a1.name, a1.math, a1.english, a1.kei);
/*24*/
/*25*/ return 0;
/*26*/ }
/*27*/
/*28*/ struct score Kei(struct score dat)
/*29*/ {
/*30*/ dat.kei = dat.math + dat.english;
/*31*/
/*32*/ return dat;
/*33*/ }
math と english の合計を求める関数 Kei を作ります。Kei の仕様についてですが、この関数は引数に struct score 型変数を取り、kei を計算した struct score 型変数を返します。関数の宣言では引数、戻り値部分ともに int などと同様、「struct score」で struct score 型変数であることを示します。ここでお気づきかもしれませんが、実は戻り値を構造体という形にすれば関数から複数の値を返すことができるのです。
さて処理の流れですが、呼び出し部分で a1 を渡します。すると普通の変数のときと同様、関数 Kei の struct score 変数 dat にその内容がコピーされます(コピーのされ方は「=」といっしょ、つまり単純に全部写す)。
関数の中では、dat のメンバ kei を計算して格納しています。この時点で dat にはすべてのメンバにデータが入ったわけです。
最後に dat を返し、呼び出し側ではその結果を a1 に格納しています(dat が a1 にコピーされる)。つまり、a1 のメンバ kei が計算されたことになります。
ですがこの方法には若干の不備があります。それは Kei が呼び出される時点で math と english が入っていると仮定していることです。つまり、もしこの二つのデータが空の場合、当然 kei も正しく計算されません。そこで、データの入力、kei(合計) の計算という関連の高い一連の動作を一つの関数にまとめてしまうことにしまいましょう。
/*01*/ #include <stdio.h>
/*02*/ #include <string.h>
/*03*/
/*04*/ struct score{
/*05*/ char name[16];
/*06*/ int math;
/*07*/ int english;
/*08*/ int kei;
/*09*/ };
/*10*/
/*11*/ main()
/*12*/ {
/*13*/ struct score SetDat(char *name, int math, int english);
/*14*/ struct score a1;
/*15*/
/*16*/ a1 = SetDat("totobon", 90, 75);
/*17*/
/*18*/ printf("a1: name -> %s, math -> %d, english -> %d, kei -> %d\n",
/*19*/ a1.name, a1.math, a1.english, a1.kei);
/*20*/
/*21*/ return 0;
/*22*/ }
/*23*/
/*24*/ struct score SetDat(char *name, int math, int english)
/*25*/ {
/*26*/ struct score dat;
/*27*/
/*28*/ strcpy(dat.name, name);
/*29*/ dat.math = math;
/*30*/ dat.english = english;
/*31*/
/*32*/ dat.kei = dat.math + dat.english;
/*33*/
/*34*/ return dat;
/*35*/ }
関数 SetDat ですが、引数に「name」「math」「english」を渡すと、これらの値とともに「kei」を計算した struct score 変数を返すようにしました。
関数本体の流れですが、難しくありません。関数内で戻り地に使う struct score 変数を用意し、そこに与えられたデータと kei を格納、これを return で返してやります。
さて、ここで追加問題です。一般的なテストなどの点は 0 ~ 100 点ですね。そこで関数にこの範囲内でない点が与えられた場合は適当な処理をするようにします。
struct score SetDat(char *name, int math, int english)
{
SCORE dat;
if(math < 0) math = 0;
if(math > 100) math = 100;
if(english < 0) english = 0;
if(english > 100) english = 100;
strcpy(dat.name, name);
dat.math = math;
dat.english = english;
dat.kei = dat.math + dat.english;
return dat;
}
余談になりますが、C++ ではこのようにある構造体と関係の深い関数を、その構造体のメンバ(メンバ関数)にしてしまい、さらに直接操作されたくない(関数を通して操作してほしい)メンバを、直接操作できない(隠してしまう:非公開)ようにすることができます。そのような構造体を class (クラス)といい、外部から直接操作できない一連のデータと、それを操作するための関数、といった概念をカプセル化といいます。C++ へのステップアップを考えている人はこのことを意識して、本章にあげたような構造体及び関数を作るようにすれば、C++ をぐっと楽にマスターできるかもしれません。
このような概念からすると、main 関数の中の printf などで「a1.math」のように直接メンバを参照されるのはイヤですね。そこでデータを表示する関数も作ってしまいましょう。
void ShowScore(struct score dat)
{
printf("name -> %s, math -> %d, english -> %d, kei -> %d\n",
dat.name, dat.math, dat.english, dat.kei);
return;
}
また、もしそれそれのデータを個別に知りたいのならば、次のような関数を用意すればよいでしょう。
int ShowMath(struct score dat)
dat のメンバ math の値を戻り値とするような関数。
この関数を通して dat の math を参照する。
さて、話は元に戻りますがこの SetDat はまだ改良の余地があります。というのも戻り値という手法を使うことで、わざわざ SetDat のなかで struct score 変数を用意しなければなりませんし、また、呼び出し側で戻り値を格納するのにわざわざ「コピー」という処理が行われています。感の鋭い人は、何を言いたいかもう分かってしまったかもしれませんね。
/*01*/ #include <stdio.h>
/*02*/ #include <string.h>
/*03*/
/*04*/ struct score{
/*05*/ char name[16];
/*06*/ int math;
/*07*/ int english;
/*08*/ int kei;
/*09*/ };
/*10*/
/*11*/ main()
/*12*/ {
/*13*/ void SetDat(struct score *dat, char *name, int math, int english);
/*14*/ void ShowScore(struct score dat);
/*15*/ struct score a1;
/*16*/
/*17*/ SetDat(&a1, "totobon", 90, 75);
/*18*/
/*19*/ ShowScore(a1);
/*20*/
/*21*/ return 0;
/*22*/ }
/*23*/
/*24*/ void SetDat(struct score *dat, char *name, int math, int english)
/*25*/ {
/*26*/ if(math < 0) math = 0;
/*27*/ if(math > 100) math = 100;
/*28*/ if(english < 0) english = 0;
/*29*/ if(english > 100) english = 100;
/*30*/
/*31*/ strcpy(dat->name, name);
/*32*/ dat->math = math;
/*33*/ dat->english = english;
/*34*/
/*35*/ dat->kei = dat->math + dat->english;
/*36*/
/*37*/ return;
/*38*/ }
/*39*/
/*40*/ void ShowScore(struct score dat)
/*41*/ {
/*42*/ printf("name -> %s, math -> %d, english -> %d, kei -> %d\n",
/*43*/ dat.name, dat.math, dat.english, dat.kei);
/*44*/
/*45*/ return;
/*46*/ }
今度は SetDat は戻り値がありません。代わりに a1 のアドレスを受け取り、そこへ直接データを入力するようにしました。これで全工程を通して用意された struct score 変数は a1 ただひとつですし、戻り値を代入(コピー)するという余計な操作も省かれました。ただ、この程度のプログラムの差なら最近のコンピュータにとってはほとんど違いがないでしょう。ですから、どちらの方法をとるかは時と場合によって使い分ければ良いかと思います。(この問題のようにどっちでも良い場合は趣味の問題ですね。)
さぁ、今度は構造体を「+」してみましょう。「えっ!だって、最初に構造体は足せないって言ったじゃないか。」はい、確かに言いました。ですがそれは「+」演算子を使ってのこと。もう分かりましたね。ここでは関数を使って「足す」という演算を自分で作ってみることにします。
/*01*/ #include <stdio.h>
/*02*/ #include <string.h>
/*03*/
/*04*/ struct score {
/*05*/ char name[16];
/*06*/ int math;
/*07*/ int english;
/*08*/ int kei;
/*09*/ };
/*10*/
/*11*/ main()
/*12*/ {
/*13*/ struct score AddStruct(struct score dat1, struct score dat2);
/*14*/ void SetDat(struct score *dat, char *name, int math, int english);
/*15*/ void ShowStruct(struct score dat);
/*16*/ struct score a1, a2, ad;
/*17*/
/*18*/ SetDat(&a1, "totobon", 90, 75);
/*19*/ SetDat(&a2, "bonto", 60, 95);
/*20*/ ad = AddStruct(a1, a2);
/*21*/
/*22*/ ShowStruct(a1);
/*23*/ ShowStruct(a2);
/*24*/ ShowStruct(ad);
/*25*/
/*26*/ return 0;
/*27*/ }
/*28*/
/*29*/ struct score AddStruct(struct score dat1, struct score dat2)
/*30*/ {
/*31*/ struct score dat;
/*32*/
/*33*/ strcpy(dat.name, "Add");
/*34*/ dat.english = dat1.english + dat2.english;
/*35*/ dat.math = dat1.math + dat2.math;
/*36*/ dat.kei = dat1.kei + dat2.kei;
/*37*/
/*38*/ return dat;
/*39*/ }
/*40*/
/*41*/ void SetDat(struct score *dat, char *name, int math, int english)
/*42*/ {
/*43*/ if(math < 0) math = 0;
/*44*/ if(math > 100) math = 100;
/*45*/ if(english < 0) english = 0;
/*46*/ if(english > 100) english = 100;
/*47*/
/*48*/ strcpy(dat->name, name);
/*49*/ dat->math = math;
/*50*/ dat->english = english;
/*51*/
/*52*/ dat->kei = dat->math + dat->english;
/*53*/
/*54*/ return;
/*55*/ }
/*56*/
/*57*/ void ShowStruct(struct score dat)
/*58*/ {
/*59*/ printf("name -> %s, math -> %d, english -> %d, kei -> %d\n",
/*60*/ dat.name, dat.math, dat.english, dat.kei);
/*61*/
/*62*/ return;
/*63*/ }
AddScore という、引数に与えられた二つの struct score 型構造体を足す関数を作りました。中身はもう見るだけで分かると思います。name は足せないので「Add」という文字列を入れておき、あとは各データを足しているだけです。