C++によるプログラミング入門19
配列とコンストラクタ

 こんにちは。もうすぐ春ですね。(って、はじめに書いたのは1997年の2月でしたが、これを書き直している今は2005年の1月です。月日のたつのははやいものですね〜。)2月は寒い月ですが、もうすぐ春、、、というか私はもう春のような気がするので、とても好きです。
 前回はデストラクタの話をしました。今回はもう一度コンストラクタの話に戻ります。コンストラクタの話と言うより、配列の話なのですが、ここでコンストラクタに対する新しい知識が必要になるのです。

 さて、「配列」の話をしましょう。配列とは同じタイプのものを集めたものです。例えば、今、100人の学生の成績を処理するプログラムを書くとします。その場合、100個の整数値をいれる「いれもの」が必要になるでしょう。「いれもの」のことを変数というのでしたね。
 この変数を

    int a,b,c,d,....

なんて100個つくるのは大変ですね(そういう人いるんですが。^^;;)。それに、すべて学生の点数ということである意味同じものなわけですから、統一的に定義し、統一的に扱える方法がほしいと思うのが人情です。これが、配列なのです。これは、例えば、次のように定義できます。

    int tensu[100];

 ここで、tensuは配列の名前で、もちろん何にしてもかまいません。これで整数を100個いれるいれものができたことになります。実際のいれものは、

    tensu[0]、tensu[1]、tensu[2]、...、tensu[99]

です。注意してほしいのは、0からはじまって99までということです。普通の人なら、数字を100数えろと言われれば、「1、2、3、4、...、99、100」と数えるところですが、C/C++の世界では、「0、1、2、...、98、99」と数えるのです。変な感じがするかもしれませんがそうなっているのです。
 今の説明だけではわかりづらいでしょう。ちょっと例をあげます。ただ、100人を相手にしたプログラムだと実行するが大変なので、10人用にします。

//hairetu_sample.cpp
//10人の学生の点数を入力し、その平均値を計算するプログラム
#include <iostream>
using namespace std;

int tensu[10];

int main()
{
    int sum = 0;  //sumは合計点、ただし、ここではじめは0と置いておく。説明参照。
    cout << "10人の学生の点数を順番に入力してください。" << endl;
    for(int i = 0; i < 10; i++){
        cin >> tensu[i];
    }
    cout << "それでは平均を計算します。" << endl;
    for(int i = 0; i < 10; i++){
        sum += tensu[i];
    }
    cout << "平均点は" << sum/10 << "です。" <<endl; 
    //上のように出力の文に計算式を書いてもかまいません。
}

 わかった人もいるかと思いますが、一応説明します。まず、1行目ですが、整数sumを定義しています。ここで、sumには0を代入しました。C++ではこのように1行でいろいろなことができてしまいます。
 最初のfor文を考えてみましょう。for文の( )の中を見ると、変数iが0から9まで動くことがわかります。つまり、このfor文で行う繰り返しは、具体的に書くと、

    cin >> tensu[0];
    cin >> tensu[1];
    cin >> tensu[2];
    ...
  ...
    cin >> tensu[9];

と同じなのです。
 最後のfor文の繰り返しの意味はわかりますか?前にでてきましたが、

    sum += tensu[i];

というのは、sumにtensu[i]を足せという意味です。sumははじめ0で、iを0から9まで動かすので、for文による繰り返しが終わると、sumは学生の点数の合計点になっているはずです。あとはこれを出力して終わり、というわけです。
 いくつかコメントがあります。まず、int tensu[10];の位置ですが、これはmain()の中カッコの中でも良いものです。中に置いても外に置いても、このくらいのプログラムでは大差ありません。
 また、整数の割り算では、小数点以下がまるめられてしまうので、このプログラムの出す「平均値」は正確なものではありません。(つまり、あくまで整数の計算ということです。)
 そして、最後になりましたが、10個の整数の合計や平均を出すのに、実は、配列を使う必要はないということもコメントしておきましょう。ちょっと考えれば、配列を使わないで同じ動作をするプログラムが書けると思います。しかし、このプログラムは、あくまで、配列の説明のためののものです。それに、配列に格納したデータを使えば、これを成績順に整列させて出力することなどもできます。その場合は、配列が重要な役割を果たすはずです。
 みなさん、どうでしょう。配列はわかったような気がしませんか?
 なお、tensu[0]のような配列でできる各変数を「配列要素」とか単に「要素」と言います。また、要素の数を要素数と言います。たとえば、今の例では、「tensuは要素数10のint型の配列」などということができます。
 ところで、今日は、クラスを使っていませんね。これはどうしたことかと、思ったとしたら、あなたはもうC++の人です。上のような形式のプログラムはC++の元になったCによく見られるもので、必ずしも、悪いプログラムとは言えません。ただ...。(この後に、私がなんと言うか、わかった人は優秀です。ちょっと、考えてみてください。)
 ただ、プログラムを拡張しづらいのです。(<−これが、正解でした。ほんと、くどいかな?)上のプログラムは、1教科についてだけ考えています。後で、別の教科が増えたらどうしましょう。まあ、そのくらいなら、簡単です。整数の配列をふやせば良いのです。でも、また、そのあと教科が増えて、平均だけでなく、最高点、最低点も出せるようにしなければならなくなり、教科によっては、特殊な得点計算(例えば、ゲタをはかすとか、、、)が必要になり、、、と、どんどんプログラムの拡張が必要になっていった場合、上のようなプログラムを拡張していくと、そのうち、何がなんだかわからなくなったり、拡張がとてつもなく面倒になったりするのです。(もちろん、プログラマの腕にもよるわけですが。)
 Cでも対策はあります。しかし、C++はこの点を特に重視しているのです。それがクラスなのです。クラスを使ったプログラムならへっちゃら、、、というつもりはありません。しかし、C++のクラスはそのような拡張を容易にするように、存在するのでした。

 実際には、どんな拡張がありうるか、現場の人が考えることであり、いろいろな状況に応じて、いろいろなプログラムが考えられます。例えば、私なら、次のようなクラスをつくります。(ただし、簡略バージョンです。念のため。)

class Gakusei
{
    int sansu;  //算数の点
public:
    Gakusei(int x) : sansu(x){}
    void set_sansu(int x){ sansu = x; }
    int get_sansu() const{ return sansu; }
};

 そもそも、点数というものは学生の「持ち物」であるので、クラス「学生」をつくり、点数はそのデータメンバにしました。上の例では、算数のみをデータにしましたが、それは、私の担当教科が、今のところ、算数だけだからです。もし、来年、国語も採点する必要がでたら、そのようにプログラムを改造するつもりです。
 上の例は、とても単純ですが、実際に使っていて、次のような問題がでたとします。(うああ。^^;

    「私は、いつもゲタ付きの点数を学生に与えている。
  そのため、処理次第で100点を越える場合もある。しかし、このプログラムは教務課提出用
  の最終成績を処理するので、100点を越えた学生の点はゲタをとって100点にしたい。」

まあ、とってつけたような例ですが、要するに、入力値に上限をつけたいということで、よくあることです。この場合は、プログラム(ができているとします)の別の部分はいじらずに、クラスを次のように変えることも考えられます。

class Gakusei
{
    int sansu;  //算数の点
public:
    //コンストラクタの中でメンバ関数を使っても問題ない。
    Gakusei(int x){ set_sansu(x); }
    void set_sansu(int x);  //定義を外に出した。
    int get_sansu() const{ return sansu; }
};

void Gakusei::set_sansu(int x){
    if(x >= 100){
        //xが100以上の時は、100を代入してしまう。
        x = 100;
    }
    sansu = x;
}

 拡張の話はこの辺にしましょう。クラスが便利そうだという事で、そのようなプログラムをつくることにしましょう。10人(本当はもっとたくさん。)の学生を扱うにはどうしたら、良いのでしょう。
そうです、配列です。(なんか今日の口調はなんですね〜。許してください。)基本的にはまず、クラスを定義して、

    Gakusei mine[10];

のようにすればよいのです。ところが、実際、前の定義ではうまくいきません。それは、コンストラクタが整数値の引数を必要としているからです。引数が与えられなければ、オブジェクトはつくられないのです。配列の定義には引数はつけられないですよね。そのため、上のようなコードはエラーになってしまうのです。
 これを避けるためのもっとも基本的な方法は、引数を持たないコンストラクタを別に用意するという方法です。この場合コンストラクタは引数ありと引数なしの二つになってしまうのです。それでいいのです!!!C++はそのようにつくられた言語です。
 このようにコンストラクタを2つ作ると、

    Gakusei one(72);

のように、学生の点数(上の例では72点)を設定しながら、オブジェクトをつくるときは、はじめに書いた引数のあるコンストラクタが使われ、配列に使うような場合には、引数なしのコンストラクタが使われることになるのです。
 ところで、引数なしのコンストラクタは何をすればよいのでしょう。それは、ケースバイケースですが、点数はどうせ後から入力されるのですから、引数なしのコンストラクタでは、何もしないでもよいでしょう。そうすると、何もしないコンストラクタの誕生です。ただ、本当に何もしていないのかと言うと、配列の定義などでとても役に立っているコンストラクタであることは忘れないでください。
 以上をまとめた例を作ります。

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

class Gakusei
{
    int sansu;  //算数の点
public:
    //引数なしのコンストラクタ(これがないとプログラムはエラー)
    Gakusei(){}  //何もしないので、中カッコ内は空
    //引数ありのコンストラクタ
    Gakusei(int x){ set_sansu(x); }
    void set_sansu(int x); 
    int get_sansu() const{ return sansu; }
    void input();    //入力のために新しく付けた
};

void Gakusei::set_sansu(int x){
    if(x >= 100){
        //xが100以上の時は、100を代入してしまう。
        x = 100;
    }
    sansu = x;
}

void Gakusei::input()
{
    int temp;
    cout << "算数の点を入力してください:";
    cin >> temp;
    set_sansu(temp);
}

Gakusei mine[10];

int main()
{
    int sum = 0;
    cout << "10人の学生の点数を入力してください。" << endl;
    for(int i = 0; i < 10; i++){
        mine[i].input();
    }
    cout << "それでは平均を計算します。" << endl;
    for(int i=0; i < 10; i++){
        sum += mine[i].get_sansu();
    }
    cout << "平均点は" << sum/10 << "です。" << endl; 
}


 今日はこんなところでしょうか。次回はいよいよ悪名高いポインタですぜ。
目次のページ
前のページ  後のページ