今回は、前回の例、さむらいたちを使って考えてみましょう。前回のプログラムでは、クラス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];
}
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];
}
(実行途中)