目次へ 1ページ 2ページ 3ページ 4ページ

2.2 生命体クラス(2) 1999.03.12(初版)  生命体のクラス設計の2つ目のバージョンは、継承を使うものです。継承とは既存の クラスからその属性と動作を受け継ぎ、その上に必要な機能を追加した新しいクラスを 作ることであり、ソフトウェア再利用の1つの形態です。  生命体の2つの状態、DYING(死んでいる)とLIVING(生きている)をクラスにするとどう なるでしょうか。これはちょっと奇妙にみえます。  状態ですから、(生命体の)DYING という状態。(生命体の)LIVING という状態です。 2つの共通部分は「生命体」です。  元になるクラスつまり基底クラスを life、 それを継承した派生クラスを dying およ びliving とします。基底クラス life は生命体から生死の状態を取り除いたものといえ ます。ですから、この基底クラスは生きているか死んでいるかが分からない抽象化された 実体のないものであることに注目しましょう(だから奇妙)。基底クラス lifeを次のよ うに書きます。
#include <iostream.h>
#include <time.h> 
#include "glibw32.h"// グラフィックス・クラスライブラリ GLIBW32 

const int N = 40; // 正方形の升目の数 
enum state{ DYING, LIVING, STATES}; 

class life; // 前方宣言 
typedef life* field[N][N]; 

// 生命体の抽象クラス 
class life{ 

public: 
    virtual state getstate() const = 0;  //状態の識別 
    virtual life* next(field) = 0;        //次の状態 
    virtual void draw(GRAPH *) = 0;       //描画関数 

protected: 
    int row, col; // 位置 
    void count(field w, int sum[]) const; 
};
ここで、データメンバのアクセス指定子が protected(限定公開)になっています。限 定公開メンバは、派生クラスのメンバからアクセスできます。  またメンバ関数が純粋仮想関数なっています。 virtual state getstate() const = 0; // 純粋仮想関数 virtual life* next(field) = 0; // 純粋仮想関数 virtual void draw(GRAPH *) = 0; // 純粋仮想関数 これは、仮想関数のプロトタイプ宣言の後に =0 というイニシャライザを付けたもの で、関数の実体の定義を派生クラスに先送りするものです。この様な純粋仮想関数を持 つクラスを抽象基底クラスといいます。実体を持たないわけですから、抽象基底クラス はオブジェクトを作ることはできません。プログラムの中で実際に作られるのは、これ から派生したクラスのオブジェクトです。それらを具象クラスと呼びます。それが次の 公開継承した2つの派生クラス dying と living です。
// 生きている生命体のクラス
class living : public life{ 

public: 
    living(int r, int c) { row = r; col = c; } // コンストラクタ 
    state getstate() const { return LIVING; } 
    life* next(field); 
    void draw(GRAPH *g){ 
        g->frectangle(row*10+3,col*10+3,row*10+7,col*10+7, WHITE); 
    } 
}; 

// 死んでいる生命体のクラス 
class dying : public life{ 

public: 
    dying(int r, int c) { row = r; col = c; } // コンストラクタ 
    state getstate() const { return DYING; } 
    life* next(field); 
    void draw(GRAPH *) { } // 何もしない 
};
 ここでの継承の仕組みは、上のクラスの定義部分にあるように  typedef life* field[N][N]; で定義された基底クラス lifeへのポインタ型 (life*) の配列要素 field[i][j]を通じ て、派生クラスのliving や dying のオブジェクトにアクセスすることができます。こ の点を明確にさせるために残ったコードを書き上げます。
// 次の世代の状態を決定する
life* living::next(field w) 
{ 
    int sum[STATES]; 
    count(w, sum); 

    if(sum[LIVING] == 2 || sum[LIVING] == 3) 
        return (new living(row, col)); 
    else 
        return (new dying(row, col)); 
} 

// 次の世代の状態を決定する 
life* dying::next(field w) 
{ 
    int sum[STATES]; 
    count(w, sum); 

    if(sum[LIVING] == 3) 
        return (new living(row, col)); 
    else 
        return (new dying(row, col)); 
}
以上で生命体クラスが完成しました。最後に、前のバージョンでのプログラムと同じイ ンターフェースを持つように、オブジェクトの生成、世代変化、オブジェクトの削除関 数を書きます。
// オブジェクトの生成
void init(field w) 
{ 
    for(int i = 0; i < N; ++i) 
        for(int j = 0; j < N; ++j) 
            w[i][j] = new dying(i,j); 
} 

// 生命体の初期設定 
void create(field w, int i, int j) 
{ 
 // 生命体の生息パターン 
    delete(w[i][j+1]); 
    w[i][j+1] = new living(i,j+1); 
    delete(w[i-1][j]); 
    w[i-1][j] = new living(i-1,j); 
    delete(w[i][j]); 
    w[i][j] = new living(i,j); 
    delete(w[i+1][j]); 
    w[i+1][j] = new living(i+1,j); 
    delete(w[i+1][j-1]); 
    w[i+1][j-1] = new living(i+1,j-1); 
} 

// 世代交代 
void update(field w) 
{ 
    field w_temp; // 一時的なポインタ 
    int i,j; 

    // 次の状態を一時的に記憶 
    for(i = 1; i < N-1; ++i) 
        for(j = 1; j < N-1; ++j) 
            w_temp[i][j] = w[i][j]->next(w); 

    for(i = 1; i < N-1; ++i) 
        for(j = 1; j < N-1; ++j){ 
          delete(w[i][j]);       // 古い状態を削除 
            w[i][j] = w_temp[i][j]; // 新しい状態をコピー 
        } 
} 

// オブジェクトの削除 
void dele(field w) 
{ 
    for(int i = 0; i < N; ++i) 
        for(int j = 0; j < N; ++j) 
          delete (w[i][j]); 
} 

// 描画関数 
void draw(GRAPH *g, field w) 
{ 
    g->cls(); // 画面消去 
    for(int i = 0; i < N; ++i) 
        for(int j = 0; j < N; ++j) 
            w[i][j]->draw(g); 

    Sleep(100); // 遅延関数(100ミリ秒間停止する) 
}
main()関数は前回のとまったく同じものですので省略します。main()関数のプログラム の流れは次のようになります。 (1)関数 init() でオブジェクトを生成する。 (2)関数 create() で生命体の第1世代を設定する (3)関数 draw() で生命体の描画 (4)関数 update() で次の世代に更新する (5)3〜4を繰り返す (6)関数 dele()でオブジェクトを削除し終了する すでに述べたように、配列要素 field[i][j]はすべて基底クラスlifeへのポインタです。 (1)の初期化では w[i][j] = new dying(i,j); のように、派生クラスのオブジェクトを動的に生成しています。つまり基底クラスへの ポインタが指しているのは派生クラスのオブジェクトです。基底クラスへのポインタを 介して派生クラスのメンバ関数にアクセスすることができます。メンバ関数が呼び出さ れるとき基底クラスのメンバ関数が仮想関数になっていると、対応する派生クラスのメ ンバ関数が呼ばれることになります。  プログラムが進行するに従って、w[i][j] が指すオブジェクトは dying であったり living であったりします。しかしその呼び出しは1つの表現でおこなえます。たとえば 関数 update() の中のコードでは w[i][j]->next(w); は、この1つのメンバ関数呼び出しで受け取るオブジェクトが dynig と living の型に 応じて2通りの応答をします。この様な機構を多態性といいます。  さらに注目すべきことは、どちらの派生クラスのメンバー関数が呼び出されるかはコ ンパイル時には決まっていないことです。プログラムが実行されてからどのクラスのオ ブジェクトのメンバ関数を呼び出すかが決定されます。これを動的束縛といいます。 ソース・ファイルはこちらです。 LIFE02.CPP(5KB) または、HTMLファイルで見る場合ははこちら

実行ファイルとソースファイルのダウンロードはこちらです、
ライフゲーム (2)
実行ファイルとソース・ファイル
Windows 95/ 98上で動作します。
  ダウンロード
  life02.lzh (47KB)

 * このソフトウェアは、SYMANTEC. NORTON AntiVirus 5.0 でウィルス検査を 
  行っています。 
 * ファイルは吉崎栄泰氏による LHA (Copyright(c)1988-92 H.Yoshizaki)を 
  使って圧縮しています。 

目次 前のページへ 次のページへ

Copyright(c) 1999 Yamada,K