C++によるプログラミング入門16
継承の練習 

こんにちは。今回は前回の復習をかねて少し練習をします。また、今までなかなか書けなかった細々したことも少しまとめて書きます。
さて、前回は「継承」という話をしたのでした。これは、前につくったクラスにいくつかの要素を付け加えて新しいクラスをつくるというもので、もとのクラスを基底クラス,新しいクラスを派生クラスなどと言うのでした。
前回は「猫」から「サラリー猫」(サラリーマン?)をつくりましたが、今回は、まず、全く同様にして「資産家猫」をつくってみてください。これは、「資産」を持つ猫で、その利子だけで生きている猫です。こんな風にしましょうか。

  クラス 資産家猫
    データメンバ:資産
    メンバ関数 :コンストラクタ(名前と資産を初期化)
           年収を表示する関数

元気な人は自分で考えてください。私なら、こんな感じにつくります。

//sisanneko.cpp
#include <iostream>
#include <string>
using namespace std;

class Neko
{
    string name;    //名前
public:
    Neko(string);   //コンストラクタ
    void naku() const;
};

Neko::Neko(string s) : name(s){}

void Neko::naku() const{
    cout << "にゃあ。俺様は" << name << "だ。" << endl;
}

class SisankaNeko : public Neko
{
    int sisan;  //資産、万円単位
public:
    SisankaNeko(string, int);     //コンストラクタ
    //年収を戻す関数。年収は資産の利子のみで、利率は2%とする。
    int get_nensyu() const{ return sisan * 2 / 100;}
};

SisankaNeko::SisankaNeko(string n, int s) : Neko(n), sisan(s){}

int main()
{
    string n;
    int s;
    cout << "資産家猫をメモリ上に生成します。名前を決めて入力してください。" << endl;
    cin >> n;
    cout << "資産を決めて入力してください。(単位:万円)" << endl;
    cout << "(数字は半角で入力してください。)" << endl;
    cin >> s;
    SisankaNeko dora(n, s);  //資産家猫の生成
    //無限ループ。抜けるにはユーザが1、2以外の数字を入力すればよい。
    while(1){
        cout << "どうしますか?" << endl;
        cout << "1 鳴かす 2 年収を表示 3 やめる" << endl;
        int ans;
        cin >> ans;
        if(ans == 1){
            //Nekoの関数nakuをSisankaNekoが使う。
            dora.naku();
            //Nekoのnaku()はSisankaNekoでも使えるのです。
        }
        else if(ans == 2){
            cout << "年収は現在" << dora.get_nensyu()<< "万円です。" << endl;
        }
        //上のどれも成り立たない場合。ユーザには「3 やめる」と表示したが、
        //1、2以外ならループを抜けるようにした。
        else{
           break;
        }
        //見やすさのための改行
        cout << endl;
    }

    cout << "おしまい" << endl;
}

どうでしょうか?それにしても、銀行にお金を預けておいて利子が2%なんて2004年12月現在、「ぶっちゃけありえねー」ですね。


少し別の例をやってみましょうか。例えば、剣士だの占い師だのの出てくるゲームもどきを考えましょう。これらの登場人物が実は単なる「現象の名前」でしかないのなら、クラスにする必要はありません。(つまり、「剣士」という人がいても、その「剣士」はつねに剣技を振るうだけで、名前も無ければ寿命もない、何も固有のデータを持たないという場合です。この場合「剣士」はクラスではなく関数で表されます。)
しかし、これらの人物たちがそれぞれ固有の何か(名前やパワーやお金など)を持っているのなら、クラスにするべきです。
ただ、実際のゲーム風にクラスを複雑にすると今日のポイントがわからなくなるので、ここでは簡単に、どの登場人物も「パワー」のみを持っていることにします。剣士は「パワー」のある限り戦い、占い師は「パワー」のある限り占う、、、という具合です。
まず、はじめに基底クラス「人物」を定義し、それから「剣士」「占い師」を派生させます。「パワー」は「剣士」にも「占い師」にも共通なので、「人物」のデータとし、「剣士」や「占い師」に固有の仕事はそれぞれのクラスで定義します。まあ、サンプルを見てみましょう。

//jimbutu_game.cpp
#include <iostream>
#include <cstdlib>  //乱数に必要、入門10を参照
#include <ctime>   //乱数に必要
using namespace std;

//人物、剣士や占い師などの基底クラス
class Jimbutu
{
    int power;
public:
    //コンストラクタ、powerを初期化
    Jimbutu() : power(5){}
    int get_power() const { return power; }
    //仕事をした後にパワーを減らす関数
    void decr_power(){
        power--;
        cout << "現在のパワー:" << power << endl;
    }
};

//剣士
class Kensi : public Jimbutu
{
public:
    void sigoto();  //剣士の仕事、定義はクラス宣言の外で、、、
};

void Kensi::sigoto()
{
    //get_power()でパワーを調べ、0以下なら何もしない。
    if(get_power() <= 0){
        cout << "すまん。疲れているんだ。" << endl;
        return;  //この関数「sigoto」から抜ける。
      }   
    cout << "俺は剣士だ。俺の剣技を見せてやる。" << endl;
    cout << "どりゃ〜。おりゃ〜。そりゃ〜。、、、以上。" << endl;
    decr_power();  //Jimbutuの関数
}

//占い師
class Uranaisi : public Jimbutu
{
public:
    void sigoto();  //占い師の仕事、定義はクラス宣言の外で、、、
};

void Uranaisi::sigoto()
{
    //get_power()でパワーを調べ、0以下なら何もしない。
    if(get_power() <= 0){
        cout << "わしゃ〜占い師じゃが、腹減って動けんよ。" << endl;
        return;  //この関数「sigoto」から抜ける
    }
    cout << "私は占い師じゃ。今日のおまえの運勢を占ってやろうかの。" << endl;
    int x = rand() % 3;
    //上の1行は「int x;
    //      x=rand()%3; 」 と同じ意味です

    if(x == 0){
        cout << "おまえの今日の運勢は最高じゃ。" << endl;
    }
    else if(x == 1){
        cout << "今日のおまえは、まあ、普通じゃな。" << endl;
    }
    else if(x == 2){
        cout << "今日はおまえの厄日じゃ。何もせん方がよい。" << endl;
    }
    decr_power();  //Jimbutuの関数
}

int main()
{
    srand( (unsigned)time( NULL ) );
    Kensi hero;    //剣士ヘロの誕生
    Uranaisi pero; //占い師ペロの誕生

    while(1){
        int x;
        cout << "どうします?" << endl;
        cout << "1 剣士に仕事をしてもらう。 2 占い師に仕事をしてもらう。 3 やめる" << endl;
        cin >> x;
        if(x == 1){
            hero.sigoto();
        }
        else if(x == 2){
            pero.sigoto();
        }
        else{
            break;
        }
    }
    cout<<"おしまい。"<<endl;
}

どの登場人物(Jimbutu)もパワーを持ち、この値は引数を取らないコンストラクタで5に設定するようにしました。現在のパワーを報告させる関数get_power()は派生クラスで使われます。関数decr_power()は、派生クラスのオブジェクトが仕事をしたときに呼び出され、その人物のパワーを減らすのです。ついでに、そのときのパワーを表示するようにしておきました。あと、日本語と英語が混じっていてすみません。これは反省してるのですが、、、こうなちゃったので、このままいきます。

いくつかの注意があります。

まずは些細なことです。main()のはじめに乱数の初期化をしていますが、ここに置くのは私の趣味ではありません。(趣味は人それぞれですが。)本来なら、入門9のTaiketu_basyoのように登場人物たち全員を「入れる」あるいは「制御する」クラスをつくり、そのクラスで初期化をしたいところです。しかし、プログラムが長くなるので、やめました。

派生クラス「剣士(Kensi)」「占い師(Uranaisi)」にはコンストラクタを定義しませんでした。これでもよいのです。mainの中では、KensiやUranaisiのオブジェクト(heroとpero)を生成していいますが、ここでは、基底クラスであるJimbutuの引数を取らないコンストラクタが使われます。

それと、ずっと気になっていたことがあります。それは、変数を定義する場所です。変数を定義する場所は、使われる前ならどこでもよいのです。しかし、どこでもよいというのは便利ですが、はっきり決められないので、一歩間違えれば不便にもなります。今までの例ではわかりやすくするのに、変数は使う直前で定義してきました。つまり、

    int x;
    cin >> x;

などと書いてきたわけです。これに間違いはありません。しかし、もし、この変数をもっと後の場所でも使いたいなら、その場所まで含めた範囲の中で、一番最初にするべきです。つまり、上のように書いておいてずっと後でまたxを使うと、半年後の自分や別の人が後の方のxを見て「あれ、このxどこで定義したものかな」と考え込んでしまうからです。そう考えてwhile(1)中のxの位置を決めました。簡単に言うと(いつもというわけではないですが)変数が使われる{ }内の一番最初が普通は妥当だと思います。要は見やすさの問題なのですが。(これでいくと、main()中のKensi hero;とUranaisi pero;の位置が少し変ですね。これは上に書いたように、本当は別のクラスをつくりたいところを省略したからです。)

それから、if(やelse)文なんですが、内容が1行のときは、{ }はいりません。つまり、例えば、

    if(x == 1){
        cout << "hello!" << endl;
    }

    if(x == 1)
        cout << "hello!" << endl;

は全く同じです。こういう書き方を嫌う人、好む人、いろいろですが、覚えておいてください。私は、明らかなときに限り、下のような書き方をします。以後注意してください。

最後にもうひとつ。if文と似たものにswitch文があります。それは、例えば、

    if(x == 0){
        cout << "おまえの今日の運勢は最高じゃ。" << endl;
    }
    else if(x == 1){
        cout << "今日のおまえは、まあ、普通じゃな。" << endl;
    }
    else if(x == 2){
        cout << "今日はおまえの厄日じゃ。何もせん方がよい。" << endl;
    }
    //サンプルにはなかったがelseを付け足す
    else{
        cout << "エラーじゃあ" << endl;
    }

の代わりに

    switch(x){
        case 0:
            cout << "おまえの今日の運勢は最高じゃ。" << endl;
            break;
        case 1:
            cout << "今日のおまえは、まあ、普通じゃな。" << endl;
            break;
        case 2:
            cout << "今日はおまえの厄日じゃ。何もせん方がよい。" << endl;
            break;
        default:
            cout << "エラーじゃあ" << endl;
    }

と書いても同じなのです。これもこれからときどき使いますので、使って慣れてください。

今日は練習と一緒に細々したこともだいぶ書きました。消化不良にならなければ良いのですが。
class Jimbutuが簡単すぎるのでありがたみが少ないですが、これがとても複雑なものだと想像して使ってください。そうすれば、ほんの数行で「剣士」や「占い師」ができてしまうことがものすごいことだとわかると思います。それに、「人物」が単純な場合でも、このようなプログラミングの方法に慣れてくると、ずいぶん楽になるような気がします。元気がまだ残っている人は、別の人物もうひとりを派生させてプログラムをつくりなおしてみてください。
目次のページ
前のページ 次のページ