まず、ちょっと、復習します。Neko(猫)という基本のクラスがあり、そこから、SalaryNeko(サラリー猫)とSisankaNeko(資産家猫)を派生させました。「サラリー猫」はデータとして、「月給」をもっていて、その「年収」は「月給」の12倍としました。「資産家猫」のデータは「資産」でその「年収」は「資産」の2パーセント(利子ですね)として与えられるのでした。また、SalaryNekoWithBonus(サラリー猫・ウイズ・ボーナス)という「猫」が「サラリー猫」から派生していて、その「年収」は「月給」の12倍に「ボーナス」を足すのでした。
さて、これらを全部「猫」のポインタで示して、get_nensyu()をうまく呼び出せれば、それぞれ、正しい年収が計算されるはずです。そのためには、get_nensyu()を仮想関数にすればよさそうです。では、どうすればよいかと言うと、前回のようにやるには基本になるクラスの「猫」のget_nensyu()の宣言の前にvirtualと書けばよいのでした。楽勝、、、と思いきや、「猫」にはget_nensyu()なんて関数はないのです。
「猫」は「サラリー猫」、「サラリー猫・ウイズ・ボーナス」や「資産家猫」の元になるクラスで、ポインタはこのクラスのものを使うのがふさわしいはずです。しかし、「猫」は基本的すぎて、年収に関する情報は持っていないのです。
このような場合、例えば、get_nensyu()を純粋仮想関数というものにして「猫」に置くという方法も使われます。ここで、置かれる関数get_nensyu()は実際には「猫」には必要のない関数です。こういうときには、
//元祖「猫」
class Neko
{
string name; //名前
public:
Neko(string); //コンストラクタ
void naku() const; //鳴く関数
//純粋仮想関数
virtual int get_nensyu() const = 0;
};
Neko::Neko(string s) : name(s){}
void Neko::naku() const{
cout << "にゃあ。俺様は" << name
<< "だ。" << endl;
}
とします。
「virtual int get_nensyu() const = 0;」の「virtual」が「仮想関数であること」を意味し、「=
0」が「純粋であること」を表しています。「純粋」とは「このクラスでは使わない、派生クラスで使う」と言う意味です。constは、この関数がオブジェクトのデータを変更しないので付けてあるのでした。これは「(純粋)仮想関数であること」とは、何も関係ありません。(つまり、constの(純粋)仮想関数も、constでない(純粋)仮想関数も同様に定義できます。違いは、単に、constがあるかないかだけです。)
このクラスで使わないので、定義は書きません。このような宣言をすると派生クラスの同名の関数がすべて仮想関数になるのは、前回と同じです。
ひとつ注意すべき事は、このように純粋仮想関数を宣言すると、そのクラスは「抽象クラス」と呼ばれるクラスになることです。そして、抽象クラスのオブジェクト(インスタンス)はつくれないのです。つまり、我々の例で言うと、改造したNekoは純粋仮想関数をメンバに持つので、抽象クラスです。抽象クラスなので、例えば、
Neko dora("ボス");
のように、オブジェクトを作れなくなったのです。(本当はこういうことをされると、クラスNekoの利用者は困ります。でも、今日は練習なので、この路線でいくことにしましょう。Nekoに「純粋」でない(しかし使われない)仮想関数を無理につくるという路線もありますが。)//sbs_neko.cpp
#include <iostream>
#include <string>
using namespace std;
//元祖「猫」
class Neko
{
string name; //名前
public:
Neko(string); //コンストラクタ
void naku() const;
//純粋仮想関数
virtual int get_nensyu() const = 0;
};
//「猫」の派生クラス「サラリー猫」
class SalaryNeko : public Neko
{
int gekkyu; //月給
public:
SalaryNeko(string, int); //コンストラクタ
//年収を戻す関数。年収は月給の12倍とする。
int get_nensyu() const { return gekkyu * 12; }
//月給を1万円増やす関数
void syoukyu() { gekkyu++; }
//新しく付け加える「月給の値を戻す関数」
int get_gekkyu() const{ return gekkyu; }
};
//「サラリー猫」の派生クラス「サラリー猫・ウイズ・ボーナス」。もう英語むちゃくちゃ。
class SalaryNekoWithBonus :public SalaryNeko
{
int bonus; //月給の何ヶ月分かを表す
public:
//コンストラクタ
//第1引数が名前、第2引数が月給、第3引数がボーナスを表す
SalaryNekoWithBonus(string, int, int);
//年収(月給+ボーナス)を戻す関数
int get_nensyu() const{return get_gekkyu()
* (12 + bonus);}
};
//「猫」の派生クラス「資産家猫」
class SisankaNeko : public Neko
{
int sisan; //資産、万円単位
public:
SisankaNeko(string, int); //コンストラクタ
//年収を戻す関数。年収は資産の利子のみで、利率は2%とする。
int get_nensyu() const{ return sisan * 2 / 100; }
};
Neko::Neko(string s) : name(s){}
void Neko::naku() const{
cout << "にゃあ。俺様は" << name
<< "だ。" << endl;
}
SalaryNeko::SalaryNeko(string s, int x) : Neko(s), gekkyu(x){} //コンストラクタ
SalaryNekoWithBonus::SalaryNekoWithBonus(string s, int g, int b) : SalaryNeko(s,
g), bonus(b){}
SisankaNeko::SisankaNeko(string n, int s) : Neko(n), sisan(s){}
int main()
{
int syurui; //Nekoの種類を一時格納する場所
string name; //名前の一時格納場所
int gekkyu; //月給の一時格納場所、簡単のため今回は昇給はさせません。
int bonus; //ボーナスが月給の何ヶ月分かを一時的に格納。
int sisan; //資産の一時格納場所
Neko *cat[5]; //5猫(ごにんと読む)分のNekoポインタ
//データの入力
cout << "5猫のデータを入力します。"
<< endl;
for(int i = 0; i < 5; i++){
cout << "職業を決めてください。"
<< endl;
cout << "1
サラリー猫(ボーナスなし) 2
ボーナスありのサラリー猫 3 資産家猫"<<endl;
cout << "(半角入力ですぜ!)"
<< endl;
cin >> syurui;
cout << "名前を入力してください:";
cin >> name;
switch(syurui){
case 1:
cout << "月給(万円単位で整数)を入力してください。"
<< endl;
cin >> gekkyu;
cat[i] = new SalaryNeko(name, gekkyu);
break;
case 2:
cout<<"月給(万円単位で整数)を入力してください。"<<endl;
cin >> gekkyu;
cout << "ボーナスは月給の何ヶ月分か入力してください。(整数で)"
<< endl;
cin >> bonus;
cat[i] = new SalaryNekoWithBonus(name, gekkyu, bonus);
break;
case 3:
cout << "資産(万円単位で整数)を入力してください。"
<< endl;
cin >> sisan;
cat[i] = new SisankaNeko(name, sisan);
break;
}
}
cout << "それでは各自自己紹介します。よろしいですか?"<<endl;
cout << "1 はい 2 いいえ" <<
endl;
cin >> syurui;
//ユーザが1以外の整数を入力したら終了
if(syurui != 1) return 0; //mainの中での「return
0;」はmainを終了させる
for(int i = 0; i < 5; i++){
cat[i]->naku();
cout<<"年収:"<<cat[i]->get_nensyu()<<"万円"<<endl;
}
//後始末
for(int i = 0; i < 5; i++)
delete cat[i];
cout<<"おしまい"<<endl;
}
(出力部分のみ)
ofstream f("test.txt");
などと、書かれます。ここで、fがファイルを表すオブジェクトになるのですが、私が勝手にfと名付けただけですから、別の名でもかまいません。ただし、このオブジェクトを使うときには、fstreamをインクルードする必要があります。これを使って、猫たちの年収を順にファイルtest.txtに書き出すには、#include <iostream>
#include <string>
#include <fstream> //ファイル操作に必要
.......
.......
int main(){
ofstream f("test.txt");
.......
for(int i = 0; i < 5; i++){
cat[i]->naku();
f << "年収:" << cat[i]->get_nensyu()
<< "万円" << endl;
}
.......
}