next up previous contents
Next: 20.5 課題 20 Up: 20. 構造体 III Previous: 20.3 構造体の配列の初期化

20.4 関数と構造体

関数の返り値は一つであり、関数の返り値を用いて値をやり取りする限りでは、通常は いくつもの値を受け取る事は出来ないが、構造体を使えば返り値に幾つもの値を持た せる事が出来る。しかし、その時には、何を返り値にするべきなのか、従って、関数 はどういう設計をすればよいのか、という設計指針の問題が生じる。構造体によって 幾つもの値を関数の返り値に出来るからと言って、行き当たりばったりに関数を作成 してはならない。あくまでも関数は機能本位に、どういう値に対して何をするものか を良く考えて作成しなければならない。

 

ここでは、ロールプレイングゲームを例に取って考えよう。 ロールプレイングゲームでは、まず hero がいる。hero は、 色々なところを歩き回るので、位置をデータに持っている必要がある。 また、hero が成長していくのならば、そのレベルを記録しておかねば ならない。ところが、レベルと言っても、攻撃力や守備力、魔法が使えるならば 魔法力、体力など様々な値が必要である。更に、hero は所持品を持ってい る場合もあるだろう。

例えば、hero の位置は、(x,y) 座標で、レベルは体力、攻撃力、守備力 があり、所持品は剣と鎧を持てるとすると、hero を表す構造体は、次の ように書ける。

例 7
        struct Position { int x,y;};   /* 位置 */
        struct Lebel {          /* レベル */
                int point;      /* 得点 */
                int life_upper; /* 体力の上限 */
                int life;       /* 体力 */
                int offence;    /* 攻撃力 */
                int defence;    /* 守備力 */
        };
        struct Item {   /* 所持品 */
                int drug;       /* 薬の量 */
                int sword;      /* 剣の強さ */
                int buckler;    /* 盾の強さ */
                int armor;      /* 鎧の強さ */
        };

        struct Hero {
                struct Position position;
                struct Lebel lebel;
                struct Item item;
        };

一方、hero が薬を使う場合は、そのための関数を 作成し、その関数の中で、体力を回復させることになる。この場合、 例えば、関数の型、引数は次のようになる。

例 8
        struct Hero use_drug(struct Hero hero){
                ...
                hero.item.drug=0;
                return hero;
        }

この場合は、hero.level.life の変更と hero.item.drug の消費という 2 つのデータに関係し、返り値も少なくとも この 2 つを返さなければならないので、struct Hero 型の構造体変数を 返している。

このように、関数の引数と返り値を考えるときは、その関数内で変更を受ける データに着目するようにすると良い。

一方、プレイヤーの生死の判断や、敵との遭遇の判断等は結果が明らかに真偽で あらわせられる場合は、関数の返り値は構造体にせずに真又は偽にしておいた方が 良いだろう。この場合、敵との戦闘などプレイヤーや敵のデータに変更 があり、なおかつ関数の返り値も欲しい ( 戦闘が終わったかどうか等 )という 場合でから、通常解決方法は 2つある。

後者の方法を使ったプログラムだと例えば次のようになる。

例 9
        int fight(struct Hero *phero, struct Enemy *penemy){
                ...
                dum = (phero->level.offence*(rand()%10)) 
                    / (penemy->level.defence + 1);
                penemy->level.life -= dum;    /* 敵へのダメージ */
                ...
                return 0;	/* 戦闘が続いている時 */
                ...
                return 1;	/* 戦闘が終わった時 */
        }

ここで注意するのは、ポインタを使用しているために、関数 fight() 内で 行ったダメージを受けた敵の体力の変更は、この関数に渡された構造体変数に たいして直接行っているために、関数が終了しても有効な点である。つまり、 ポインタを使う事で、関数の中から直接外部のデータを書き換えている訳である。 こうした方法がグローバル変数を使った方法よりも優れている点は、グローバル 変数を用いるとプログラム全体でその変数の使用について注意しなければならない のに対して、ポインタを用いるとそのポインタを使っている関数と呼び出し元で のみ変数の使用を考えれば良いので、比較的に考慮する範囲が狭くなるという点で ある。これは大きなプログラムを作る場合に間違いを減らすために非常に重要な 事である。



Noriyo Kanayama