C++によるプログラミング入門23
仮想関数続き

 こんにちは。C++入門もあと2、3回です。はりきっていきましょう。
 このホームページをはじめた頃には、こんな風になるとは思ってもいなかったのですが、まあ、よく続きましたね。みなさんのご訪問、励ましに感謝します。
 前回は仮想関数の紹介でした。今回はこれがどんな風に使われるかちょっとだけ見てみましょう。
 簡単に言うと、「クラス、継承、仮想関数を有効に使いこなすプログラム」を書くことが、オブジェクト指向プログラミングと呼ばれるものです。(もちろん、これはとりあえずの簡単な言い方なので、専門家の人は怒らないでくださいね。)

 今回は、前回の例、さむらいたちを使って考えてみましょう。前回のプログラムでは、クラスHito(人)をつくって、そこからSamurai(さむらい)、Ninja(忍者)、Matimusume(町娘)を派生させました。また、それぞれにJikosyoukai()(自己紹介)という関数を定義しました。この「自己紹介関数」は、仮想関数にしたので、基底クラスHitoのポインタを経由して呼び出されるても、ちゃんとそれぞれが正しく自己紹介してくれるのでした。
 なぜ、わざわざ基底クラスのポインタを使うかというと、それによって、「さむらい」も「忍者」も「町娘」も同様に扱えるからです。
以下に簡単な例を紹介しますが、ちょっとだけ注意を先にします。まず、いろいろなオブジェクトを同様に扱うためにHitoのポインタを複数用意する必要があります。そのために、ここでは「ポインタの配列」を作ります。これは、

    Hito *x[5];

などと定義するのです。こうすると、x[0]からx[4]までの5つの変数ができますが、これはHitoのポインタになるのです。(こう書くとそうなるという約束があるだけです。)このポインタにオブジェクトのアドレスを代入していきます。そうしておいて仮想関数を使うのです。では。

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

class Hito
{
    int power;
public:
    Hito(int x) : power(x){}
    virtual ~Hito(){}; //仮想デストラクタ
    void set_power(int x){ power = x; }
    int get_power() const{ return power; }
    virtual void Jikosyoukai(); //仮想関数になる
};

//Hitoの派生クラスSamurai
class Samurai : public Hito
{
public:
    //Hitoのコンストラクタにxを渡す以外は何もしないコンストラクタ
    Samurai(int x) : Hito(x){}
    //Hitoと同名のメンバ関数(これも仮想関数になる) 
    void Jikosyoukai(); //基底クラスで同名の関数にvirtualを付けたので、ここにvirtualはいらない
};

//Hitoの派生クラスNinja
class Ninja : public Hito
{
public:
    //Hitoのコンストラクタにxを渡す以外は何もしないコンストラクタ
    Ninja(int x) : Hito(x){}
    void Jikosyoukai();
};

//Hitoの派生クラスMatimusume
class Matimusume : public Hito
{
public:
    //Hitoのコンストラクタにxを渡す以外は何もしないコンストラクタ
    Matimusume(int x) : Hito(x){}
    void Jikosyoukai();
};

void Hito::Jikosyoukai()
{
    power--; //自己紹介に力を使ってpowerを1減らすことにする
    cout << "俺は人だ。" << endl;
    cout << "俺のパワーは" << power << "だ。" <<endl;
}

void Samurai::Jikosyoukai()
{
    set_power(get_power() - 1);
    //上はpowerの値をget_powerで取り出し、それから1減らした値をpowerにセットしている
    //つまり、これでpowerを1減らしたことになる
    cout << "俺はさむらいだ。" << endl;
    cout << "俺のパワーは" << get_power() << "だ。" << endl;
}

void Ninja::Jikosyoukai()
{
    set_power(get_power() - 1);
    cout << "拙者は忍者でござる。" << endl;
    cout << "拙者のパワーは" << get_power() << "でござる。" <<endl;
}

void Matimusume::Jikosyoukai()
{
    set_power(get_power() - 1);
    cout << "あたいは江戸っ娘よ。" <<endl;
    cout << "あたいのパワーは" << get_power() << "よ。" <<endl;
}

int main()
{
    Hito *x[5];    //Hitoへのポインタを5つ用意

    //オブジェクトの生成と、そのアドレスのポインタへの代入
    x[0] = new Samurai(12);
    x[1] = new Samurai(15);
    x[2] = new Ninja(7);
    x[3] = new Ninja(8);
    x[4] = new Matimusume(18);

    //以下2行がこのサンプルのポイント
    for(int i = 0; i < 5; i++)
        x[i]->Jikosyoukai();


    //オブジェクトの破棄
    for(int i = 0; i < 5; i++)
        delete x[i];

}

 main以外は前回と同じです。(前回に定義したクラスを書いているだけです。)
 上の例でポイントは、mainの中の2行だけです。

    for(int i = 0; i < 5; i++)
        x[i]->Jikosyoukai();

 5人の登場人物にそれぞれ自己紹介をしてもらうのに、このように2行で済んでしまうところを見てもらいたかったのです。つまり、上のプログラムでは、5人のいろいろな人をfor文で一気に処理しても、つまり、場合分けをしなくても、それぞれのオブジェクトの種類によって正しい関数が呼び出されるのです。これが仮想関数の良いところなのです。
 う〜ん、でも、それほど良く見えませんね。それはfor文の前の初期設定がごたごたしているからでしょう。言い訳をすると、一般的に言って、初期設定や入力部分はどうもすっきりしません。例えばファイルからの入力などなら、比較的きれいになりますが、いつもそうとは限らないのです。ただ、(ここからが言い訳なのですが)本当のプログラムでは「初期設定」(や「入力」)に比べて「情報処理」の部分が大きな割合を占めると思います。その「情報処理」の部分は、上のfor文のように、異なる種類のデータを場合分けせずに一括処理できるので、これは大変な「プログラムの簡略化」になるはずなのです。上の例は短すぎて、このありがたさがあまり見えず、初期設定のごたごたばかりが目に付いているのです。つまり、あとは想像力で私の例を補っていただきたいのです。

 ここで、ちょっと練習に、上のプログラムを、ユーザがデータを入力していくプログラムに変えてみます。ただし、データの数(登場人物の数)は、簡単のため、5に固定したものにします。
 main()以外は、いつも同じものを使えます。ほんとに、クラスを使うプログラムは楽ですよね。(ちょっと強引でしたか?)

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

class Hito
{
    int power;
public:
    Hito(int x) : power(x){}
    virtual ~Hito(){}; //仮想デストラクタ
    void set_power(int x){ power = x; }
    int get_power() const{ return power; }
    virtual void Jikosyoukai(); //仮想関数になる
};

//Hitoの派生クラスSamurai
class Samurai : public Hito
{
public:
    //Hitoのコンストラクタにxを渡す以外は何もしないコンストラクタ
    Samurai(int x) : Hito(x){}
    //Hitoと同名のメンバ関数(これも仮想関数になる) 
    void Jikosyoukai(); //基底クラスで同名の関数にvirtualを付けたので、ここにvirtualはいらない
};

//Hitoの派生クラスNinja
class Ninja : public Hito
{
public:
    //Hitoのコンストラクタにxを渡す以外は何もしないコンストラクタ
    Ninja(int x) : Hito(x){}
    void Jikosyoukai();
};

//Hitoの派生クラスMatimusume
class Matimusume : public Hito
{
public:
    //Hitoのコンストラクタにxを渡す以外は何もしないコンストラクタ
    Matimusume(int x) : Hito(x){}
    void Jikosyoukai();
};

void Hito::Jikosyoukai()
{
    power--; //自己紹介に力を使ってpowerを1減らすことにする
    cout << "俺は人だ。" << endl;
    cout << "俺のパワーは" << power << "だ。" <<endl;
}

void Samurai::Jikosyoukai()
{
    set_power(get_power() - 1);
    //上はpowerの値をget_powerで取り出し、それから1減らした値をpowerにセットしている
    //つまり、これでpowerを1減らしたことになる
    cout << "俺はさむらいだ。" << endl;
    cout << "俺のパワーは" << get_power() << "だ。" << endl;
}

void Ninja::Jikosyoukai()
{
    set_power(get_power() - 1);
    cout << "拙者は忍者でござる。" << endl;
    cout << "拙者のパワーは" << get_power() << "でござる。" <<endl;
}

void Matimusume::Jikosyoukai()
{
    set_power(get_power() - 1);
    cout << "あたいは江戸っ娘よ。" <<endl;
    cout << "あたいのパワーは" << get_power() << "よ。" <<endl;
}

int main()
{
    Hito *x[5];    //Hitoへのポインタを5つ用意
    int temp, power;

    //オブジェクトの生成と、そのアドレスのポインタへの代入
    cout << "5人のデータを順次入力してください。" << endl;
    for(int i = 0; i < 5; i++){
        cout << "選択してください:" << endl;
        cout << "1 さむらい 2 忍者 3 町娘" << endl;
        cin >> temp;
        cout << "パワーを入力してください:" << endl;
        cin >> power;
        //switch文を使います。入門16を参照してください。
        switch(temp){
            case 1:
                x[i] = new Samurai(power);
                break;
            case 2:
                x[i] = new Ninja(power);
                break;
            case 3:
                x[i] = new Matimusume(power);
                break;
        }
    }

    cout << "それでは各自自己紹介します。よろしいですか?"<<endl;
    cout << "1 はい 2 いいえ" << endl;
    cin >> temp;
    //ユーザが1以外の整数を入力したら終了
    if(temp != 1) return 0; //mainの中での「return 0;」はmainを終了させる

    //自己紹介
    for(int i = 0; i < 5; i++)
        x[i]->Jikosyoukai();

    //オブジェクトの破棄
    for(int i = 0; i < 5; i++)
        delete x[i];
}


(実行途中)

 まあ、こんなもんでしょうか。元気のある人は自己紹介のあとにパワーの合計を出すコードを入れて見てください。
 ちょっと注意してほしいことは、上のサンプルでは、ユーザが入力ミスをすると非常にまずいということです。本当はユーザの入力ミスに対する対策をプログラム中にいれるのですが、面倒ですので、省略しました。ですから、入力ミスをしないように使ってください。(笑)

 ポインタと仮想関数を使うと、「似て非なるデータ」を同じコードで処理できる、ということがポイントでした。ただし、この仕組みを有効に使いこなすのはそんなにやさしくないようです。ぜひここで練習して慣れておいてください。
目次のページ
前のページ  後のページ