C++によるプログラミング入門15
継承

みなさん、いかがお過ごしでしょうか?

さて今日は「継承」について説明します。
さて、入門5、6あたりでやった猫クラスを思い出して下さい。たしか

class Neko
{
    string name;
public:
    Neko(string s);   //「Neko(string);」でも可
    void naku() const;
};

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

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

こんなのでした。(nakuの定義がクラス定義の中にあるものと外にあるものがありましたが、ここでは、外にあるものを使います。これはどちらでもよく、中に定義があっても以下の議論は同じです。)
ここで、別の猫を考えることにします。どんなのでもいいですが、私の独断で、月給をもらって生きている「サラリー猫」にしましょう。この猫も「ただの猫」と同様に、名前があって、鳴くものとします。さらに、年収を報告する関数と昇給があることにしましょう。
「仕様書」を書くとしたら、下のような感じでしょうか?
  クラス サラリー猫
    データメンバ:名前                //ただの猫にもあった。
           月給
    メンバ関数 :コンストラクタ    //ただの猫にもあったが、ちょっと違う。
           鳴く         //ただの猫にもあった。
           年収を報告する関数 
           昇給      
「年収を報告する関数」というのは変な名ですが、ようするに、年収の値をreturnで戻してくれる関数です。(以下のコードを参照してください。)
上のようにしてみると、もともとの「猫」との共通点も多いですね。実際問題、「サラリー猫」くらいのクラスなら、はじめから書き直してもたいした手間はかかりません。しかし、ここでは大きなクラスで、もう一度書き直すのがとても大変な場合を想像してください。「猫」にあって「サラリー猫」にもある部分をもう一度書き直さないですめば、そのほうがずっといいはずです。(さらに「同じようなコードをたくさん書くと、管理が大変になる」という問題もあります。)
それを可能にするのが、「クラスの派生」というものです。これは、「猫」を元(もと)にして、「サラリー猫」のクラスを定義することです。どのようにするかというと、クラス「猫」の定義の後に

class SalaryNeko : public Neko
{
    int gekkyu;   //月給
public:
    //コンストラクタ。本文で説明します。
    SalaryNeko(string s, int x) : Neko(s), gekkyu(x){}
    //年収を戻す関数。年収は月給の12倍とする。
    int get_nensyu() const { return gekkyu * 12; }
    //月給を1万円増やす関数
    void syoukyu() { gekkyu++; }
};

と書けばよいのです。
はじめの1行目のclass SalaryNeko : public Nekoは、「class SalaryNekoをclass Nekoを元につくるよ」という意味です。ここでてきたpublicの意味はNekoのパブリックなメンバをSalaryNekoがそのまま使うという意味なのです。私の入門講座ではいつも書く「おまじない」と思っていても大丈夫です。
nameはNekoにあるので、SalaryNekoでもう一度定義する必要はありません。関数naku()も同様です。それに対して、get_nensyuとsyoukyuはまったく新しい関数なので、書き足しました。
ちょっと面倒なのはコンストラクタです。SalaryNekoはNekoを拡張したものなので、NekoがSalarayNekoの中にあると思ってください。

Nekoにはnameがあり、SalaryNekoにはgekkyuがあります。これらはユーザかプログラマから値をもらって、コンストラクタで初期化しなければならないのです。ここでは、規則がどうした、というより、

SalaryNeko(string s, int x) : Neko(s), gekkyu(x){}

の形をそのまま飲み込んでください。(こんなんばっかりですが、そうなんです。)まず、はじめのSalaryNeko(string s, int x)はコンストラクタが二つの引数「文字列」と「整数」を取ることを示しています。このうち、「文字列」はNekoのnameに格納するものなので、Nekoのコンストラクタに、そのまま渡してしまえばよいですね。Neko(s)はそういう意味になるのです。そしてgekkyu(x)は、xの値をgekkyuに格納せよという意味でした。こうすると、コンストラクタ本体ですることがなくなったので、最後の中カッコの中には何も書いていないわけです。
こうしてできたのがSalaryNekoです。このようなやりかたで、プログラミングがぐんとやさしくなったという感じは受けないかもしれませんが、大きくて複雑なプログラムでは有用な機能であると想像できると思います。
なお、上のNekoクラスのような「元になるクラス」は基底クラス、SalaryNekoのような「新しく作られるクラス」は派生クラスとよばれます。また、上のような状況を「SalaryNekoはNekoを継承している」などというのです。
最後にこの派生クラス(あるいは継承)を使ったプログラムを示します。

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

//元祖「猫」
class Neko
{
    string name;
public:
    Neko(string s);   //「Neko(string);」でも可
    void naku() const;
};

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

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

//「猫」の派生クラス「サラリー猫」
class SalaryNeko : public Neko
{
    int gekkyu;   //月給
public:
    //コンストラクタ。本文で説明します。
    SalaryNeko(string s, int x) : Neko(s), gekkyu(x){}
    //年収を戻す関数。年収は月給の12倍とする。
    int get_nensyu() const { return gekkyu * 12; }
    //月給を1万円増やす関数
    void syoukyu() { gekkyu++; }
};

int main()
{
    cout << "サラリー猫をメモリ上に生成します。名前を決めて入力してください。" << endl;
    string temp;
    cin >> temp; 
    cout << "初任給を決めて入力してください。" << endl;
    cout << "(1万円単位で、半角整数を入力してください。)" << endl;
    int syoninkyu;
    cin >> syoninkyu;
    SalaryNeko dora(temp,syoninkyu);  //サラリー猫の生成
    //ループ。抜けるにはユーザが4か1、2、3以外の数字を入力すればよい。
    while(1){
        cout << "どうしますか?" << endl;
        cout << "1.鳴かす 2.年収を表示 3.昇給 4.やめる" << endl;
        int ans;
        cin >> ans;
        if(ans == 1){     //もし、ansが1なら、、、
            //Nekoの関数NakuをSalaryNekoが使う。
            dora.naku();
            //Nekoのnaku()はSalaryNekoでも使えるのです。
        }
        else if(ans == 2){ //そうじゃなくて、ansが2なら、、、
            //SalaryNekoの関数get_nensyuをSalaryNekoが使う。
            cout << "年収は現在" << dora.get_nensyu() << "です。" << endl;
        }
        else if(ans == 3){   //そうじゃなくて、ansが3なら、、、
            // SalaryNekoの関数syoukyuをSalaryNekoが使う。
            dora.syoukyu();
            cout << "1万円昇給しました。" << endl;
        }
        else{    //上のどれも成り立たない場合
            //ユーザには4と入力すれば「やめる」になると表示しているが、
            //ほかの数字も含めて上の1、2、3以外ならループを抜けるようにした。
           //ループから抜けるにはbreak;
           break;
        }
        //見やすさのための改行
        cout << endl;
    }
    cout << "おしまい" << endl;
}

このプログラムでは「else if」を使いました。これは「そうではなくて、次の条件が成り立つならば」という程度の意味です。一応、入門7の下の方にまとめを書いておきました。

なお、SalaryNekoのコンストラクタも、クラス定義の中では宣言だけにし、コンストラクタの定義はクラス定義の外に書くこともできます。その場合、次のようになります。

//「猫」の派生クラス「サラリー猫」
class SalaryNeko : public Neko
{
    int gekkyu; //月給
public:
    //コンストラクタの宣言
    SalaryNeko(string s, int x);
    //年収を戻す関数。年収は月給の12倍とする。
    int get_nensyu() const { return gekkyu * 12; }
    //月給を1万円増やす関数
    void syoukyu() { gekkyu++; }
};

//コンストラクタの定義
SalaryNeko::SalaryNeko(string s, int x) : Neko(s), gekkyu(x){}

今日はこの辺にしておきましょうね。次回にもう少し継承の練習をするので、今回の分を見ただけであれこれ悩まないでください。自分流にやろうとすると困ることがまだたくさんあると思います。悩むより、今日のプログラムをもう少しよいものに改良してみてください。


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