C++によるプログラミング入門8
コンストラクタの引数

 こんにちは。最近、夜起きていられなくなりました。これまでは、夜更けるとますます元気が出てくる典型的夜型人間だったのに。年ですかね。おかげで、ただでさえ遅い更新が、どんどん遅くなってます。待っている方がいたら、すみません。^^;)
 今回は、

  クラス コップ
    データメンバ: 中の水(の量)
    メンバ関数 : コンストラクタ
              水を出す

というクラスを例に考えていきたいと思います。あまりおもしろい例ではないかもしれませんが、クラスの練習のためと、あと少し、言い残したことを説明するためです。

 このようなクラスを作ろうとしたらどうすればよいでしょう。
 まず、データメンバですが、これは「 int nakami;」 などとすればよいですね。これは「これから使う変数 nakami は整数である」という意味でした。
 コンストラクタは メンバ変数を初期化するのに使えるのでした。(「初期化」とは、「最初の値を格納する」などという意味でした。)
 「コップの中身は、当然、はじめ0だろう」と思う人が多いでしょうか。それでもよいのですが、今回は、はじめの水量を10ということにしておきましょう。コンストラクタではnakamiを10にすることにします。
 水を出す関数 dasu() はとりあえず、一回に2だけ出すことにしましょうか。これらをまとめると

class Glass //コップは英語でGlassかな
{
    int nakami;                    //水の量
public:
    Glass() : nakami(10){}         //引数を取らないコンストラクタ

    void dasu(){ nakami -= 2; }    //水を出す関数
    //「nakami -= 2;」はnakamiを2減らせという意味でした。
};

ができます。
 ここで、コンストラクタは引数を取らないようにしました。上に書いたように、「はじめのコップの中の水量は、いつも10」ということにするなら、「水量を指示してもらう」など、外からデータを受け取る必要がないからです。
 引数を取らないコンストラクタをどのように使うかは、このクラスを使った簡単なプログラムを見ればわかると思います。それは、たとえば、次のようなものです。

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

class Glass
{
    int nakami;                    //水の量
public:
    Glass() : nakami(10){}         //引数を取らないコンストラクタ

    void dasu(){ nakami -= 2; }    //水を出す関数
    //「nakami -= 2;」はnakamiを2減らせという意味でした。
};

int main()
{
    Glass glass; //glassという名のコップを生成。(以下の解説を参照してください。)
    //引数を取らないコンストラクタが呼び出され、中に10の水を入れられる。
    cout << "コップglassをつくりました。" << endl;
    cout << "glassから水を出します。" << endl;
    glass.dasu();
    cout<<"終了"<<endl;
}

Fig.1 Glass.exeの実行画面

 あっさり書いてしまいましたが、

    Glass glass;

と書くと、オブジェクトglassが生成され、その際に、引数を取らないコンストラクタが呼び出されることになっているのです。glassのうしろに何も書いていない(「Glass g(20);」などとなっていない)ということは、外からデータを受け取っていないということです。そして、「外からデータを受け取らないでオブジェクトを生成する」コンストラクタが、「引数を取らないコンストラクタ」なのです。これで、「引数を取らないコンストラクタ」が呼び出され(つまり実行され)、「水量10のGlassオブジェクト」が生成されることになるのです。

 ただし、いくつか不満があります。(何の役に立つかといういつもの問題はおいといて、、、。)まず、コップの中身を表示していないので、中身が本当に減ったのかどうかもわかりません。
 そこで、dasuという関数内で、残りの水の量も表示することにします。この場合、dasuの定義が長くなるので、クラス定義の外で定義することにします。

class Glass
{
    int nakami;                    //水の量
public:
    Glass() : nakami(10){}         //引数を取らないコンストラクタ
    void dasu();                   //水を出す関数(定義は外で書く)
};

void Glass::dasu(){
    nakami -= 2;
    cout << "水を出しました。" << endl;
    cout << "現在のコップの中身は" << nakami << "です。" << endl;
}

 このクラスを使って、コップから水を3回出すプログラムを書いてみましょう。

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

class Glass
{
    int nakami;                    //水の量
public:
    Glass() : nakami(10){}         //引数を取らないコンストラクタ
    void dasu();                   //水を出す関数(定義は外で書く)
};

void Glass::dasu(){
    nakami -= 2;
    cout << "水を出しました。" << endl;
    cout << "現在のコップの中身は" << nakami << "です。" << endl;
}

int main()
{
    Glass glass; //glassという名のコップを生成。
    //引数を取らないコンストラクタが呼び出され、nakamiに10が入れられる。
    cout<<"コップglassをつくりました。"<<endl;
    cout<<"glassから水を3回出します。"<<endl;
    glass.dasu();
    glass.dasu();
    glass.dasu();
    cout<<"終了"<<endl;
}

ですね。

Fig.2 Glass2.exeの実行画面

 これでコップの中の水の量もわかるプログラムになりました。何度も水を出すにはmain()の中の繰り返しを増やせばよいわけです。
 しかし、このように何度も水を出していると、最後には水がなくなるはずです。その場合はもう水は出せないはずですね。これもコードに書いてみます。(うーん。前回のロケットに似てきましたね。^^)それは、

class Glass
{
    int nakami;                    //水の量
public:
    Glass() : nakami(10){}         //引数を取らないコンストラクタ
    void dasu();                   //水を出す関数
};

void Glass::dasu(){
    if(nakami >= 2){
        nakami -= 2;
        cout<< "水を出しました。" << endl;
        cout<< "現在のコップの中身は" << nakami << "です。" <<endl;
    }
    else{
        cout<< "そんなに水がありません。" <<endl;
        cout<< "現在コップの中には" << nakami << "入っているだけです。" <<endl;
    }
}

などすればよいでしょう。
 「if(nakami >= 2)」は「もしnakamiが2以上ならば」などと読み、elseは「そうでなければ」などと読み、それぞれ条件が正しい場合にはそのあとの中カッコの中が実行されるのでした。コップの中の水が2より小さくなれば、もうそんなに水を出せないので、nakamiが2以上かどうかをチェックするようにしたのです。元気のある人はこのクラスを利用するプログラムを考えてみてください。


 ところで、前回までのクラスの作り方を真似すると、はじめにnakamiをプログラマかユーザが決められるようにできますね。これはコンストラクタに引数をつければよいのでした。ついでに、dasuの方もユーザが出す量を決められるように、引数をつけてみましょう。

class Glass
{
    int nakami;                    //水の量
public:
    Glass(int x) : nakami(x){}     //引数を取るコンストラクタ
    //ユーザかプログラマに渡される値(xで表される)をnakamiに格納
    void dasu(int);                //水を出す関数 
};

void Glass::dasu(int x){
    if(nakami >= x){
        nakami -= x;
        cout<< "水を出しました。" << endl;
        cout<< "現在のコップの中身は" << nakami << "です。" <<endl;
    }
    else{
        cout<< "そんなに水がありません。" <<endl;
        cout<< "現在コップの中には" << nakami << "入っているだけです。" <<endl;
    }
}

これを使ったプログラム例は、もうつくれるでしょうか。一応書きます。例えば、

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

class Glass
{
    int nakami;                    //水の量
public:
    Glass(int x) : nakami(x){}     //引数を取るコンストラクタ
    //ユーザかプログラマに渡される値(xで表される)をnakamiに格納
    void dasu(int);                //水を出す関数 
};

void Glass::dasu(int x){
    if(nakami >= x){
        nakami -= x;
        cout<< "水を出しました。" << endl;
        cout<< "現在のコップの中身は" << nakami << "です。" <<endl;
    }
    else{
        cout<< "そんなに水がありません。" <<endl;
        cout<< "現在コップの中には" << nakami << "入っているだけです。" <<endl;
    }
}

int main()
{
    int x;  //整数の「いれもの」xの定義。下のcinの前ならどこにあってもよいのです。
    cout << "コップを生成します。どれだけ水をいれるか入力してください。" << endl;
    cin >> x;
    Glass glass(x);  //引数を取るコンストラクタが呼び出され、
                     //水がxだけ入ったglassという名のコップが生成される
    cout << "さあ、glassから水を出します。いくら出しますか。入力してください。" <<endl;
    cin >> x;        //上のxを使いまわしている
    glass.dasu(x);
    cout<<"終了"<<endl;
}

となるでしょう。

Fig.3 Glass3.exeの実行例1

Fig.4 Glass3.exeの実行例2


 これまで、どのクラスにも、コンストラクタ(クラスと同名のメンバ関数)を書いてきました。これを書かないとどうなるでしょう。
 実は、コンストラクタを書かないと、「引数を取らないコンストラクタ」が自動的に付けられることになっています。ただし、このコンストラクタは「(特別なことは)何もしないコンストラクタ」です。そのため、メンバ変数の値は(基本的には)めちゃめちゃなものになっています。めちゃめちゃな値をまちがって使ってしまわないためには、コンストラクタをなるべく書いた方がよいのです。
 ところで、Glass3.cppのGlassのように、「引数を取るコンストラクタ」(だけ)を定義してしまうと、今度は、Glass.cppやGlass2.cppで書いたような

Glass glass;

というコードを(mainの中などに)書くと、コンパイル時にエラーになってしまいます。
 
このコードは、外からデータを受け取らないようにオブジェクトを生成しているのに、Glass3.cppのGlassには「引数(つまり外からのデータ)を(受け)取るコンストラクタ」しかないからです。上で、「コンストラクタを書かないと、引数を取らないコンストラクタが自動的に付けられることになっています」と書きましたが、それは「コンストラクタを(まったく)書かなかった場合」です。1つでも「引数を取るコンストラクタ」を書いてしまうと、「引数を取らないコンストラクタ」が自動で付けられることはなくなるのです。
 そこで、「引数を取るコンストラクタ」をGlass2.cppのように「引数を取らないコンストラクタ」に書き直してしまえば、上のコードはエラーでなくなります。
 しかし、今度は、

Glass glass(x);

のようなコードがコンパイル時にエラーになってしまいます。(ここでxはint型変数のつもりですが、もちろん、「20」など整数値そのものを入れても同じです。)これは、外からデータを受け取ってオブジェクトを生成しようとするコードなのに、「引数(つまり外からのデータ)を(受け)取るコンストラクタ」がなくなってしまったからです。

 これはGlassをどう使うかという問題になります。もし「Glass glass;」のように使うなら、「引数を取らないコンストラクタ」を書いておけばよく、もし「Glass glass(x);」のように使うなら、「引数を取るコンストラクタ」を書いておけばよいのです。
 しかし、もし、両方の使い方をしたければどうすればよいのでしょう。そのようなこともあります。
 そういうときは、「引数を取らないコンストラクタ」と「引数を取るコンストラクタ」の両方を書いておけばよいのです。実は、「引数の異なる同名のメンバ関数(コンストラクタも含む)」をいくつ書いてもよいのです。したがって、コンストラクタを2つ書いておけば、適当な方が呼び出されることになっているのです。
 以下に簡単なサンプルをお見せします。サンプル内では、2つのGlassオブジェクトを、1つは引数を取らないコンストラクタで、1つは引数を取るコンストラクタで生成しています。

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

class Glass
{
    int nakami;                 //水の量
public:
    //コンストラクタを2つ書く
    Glass() : nakami(10){}      //引数を取らないコンストラクタ
    Glass(int x) : nakami(x){}  //引数を取るコンストラクタ
    void dasu(int);             //水を出す関数
};

void Glass::dasu(int x){
    if(nakami - x >= 0){
        nakami -= x;
        cout<< "水を出しました。" << endl;
        cout<< "現在のコップの中身は" << nakami << "です。" <<endl;
    }
    else{
        cout<< "そんなに水がありません。" <<endl;
        cout<< "現在コップの中には" << nakami << "入っているだけです。" <<endl;
    }
}

int main()
{
    int x;
    cout << "水量10のコップ(glass)を生成します。" << endl;
    Glass glass;        //引数を取らないコンストラクタが呼び出される
                        //水が10だけ入ったglassという名のコップが生成さる
    cout << "さあ、glassから水を出します。いくら出しますか。入力してください。" <<endl;
    cin >> x;           //上のxを使いまわしている
    glass.dasu(x);

    cout << "水量20のコップ(glass2)を生成します。" << endl;
    Glass glass2(20);    //引数を取るコンストラクタが呼び出される
                         //水が20だけ入ったglass2という名のコップが生成される
    cout << "さあ、glass2から水を出します。いくら出しますか。入力してください。" <<endl;
    cin >> x;            //上のxを使いまわしている
    glass2.dasu(x);
    cout<<"終了"<<endl;
}

Fig.5 Glass4.exeの実行画面

 オブジェクト生成時に、2つ書いたコンストラクタのうち、どちらが呼び出されるかは、生成時に与える値の型(種類)や個数によって決められるのです。「Glass glass;」の場合、「データを与えていない」のだから「データを受け取らない」つまり「引数を取らないコンストラクタ」が呼び出され、「Glass glass2(20);」の場合は、「整数値を与えている」のだから「整数のデータを受け取る」つまり「整数(int)の引数を取るコンストラクタ」が呼び出されるわけです。
 コンストラクタを複数書くことは普通に行われます。ここでなじんでみてください。


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