C++によるプログラミング入門10
乱数

 こんにちは。前回のプログラムはまだまだ未完成ですが、それでもかなり長かったですね。しばらくあんなのはありません。あれを完成させるため、今日から少し地道な勉強をします。
 なお、これからの内容の多くはCと共通の内容です。C経験者も退屈しないようにクラスをつくる例も挙げていこうとは思うのですが、、、、。まあ、とばし読みしてください。
 今日は、入門9で出てきた「新しいこと」の説明をします。
 まず、でたらめな数、つまり乱数の話です。「乱数を発生させる」ということは「さいころを振る」というようなことです。ただ、さいころの場合は1から6の数しかでませんが、プログラムでは自由に選べます。
 まず例を見ましょう。次のようなプログラムをコンパイルし実行してみてください。

//ransu.cpp
#include <iostream>
#include <cstdlib>   //rand()を使うために必要
using namespace std;

int main()
{
    cout << rand() << endl;
    cout << rand() << endl;
    cout << rand() << endl;
}

Fig.1 ransu.exeの実行
(出力される乱数は環境によって異なるはずです。
みなさんの環境で実行したものが、Fig.1と異なっていても問題ありません。)

 このプログラムを実行すると(正しくはコンパイルし、リンクしたものを実行すると、ですが、意味は通りますよね)、3つのでたらめな整数(正確には0以上の整数)が出力されるはずです。上のプログラムで、rand()というのは、「でたらめな整数(乱数)を発生させる関数」で、「この関数が書いてある場所に、乱数を置いてくれる」ものなのです。したがって、main()の中に「cout << rand() << endl;」と書いておけば、そこで乱数が表示されるのです。

 ところで、プログラム中でcoutなどを使う場合、プログラムにその情報を持ってくる必要があり、それが「#include <iostream>」なのでした(入門3を参照)。この行があるから、coutなどが使えるわけです。
 同様に乱数を発生させるrand()という関数も「#include <cstdlib>」がないと使えません。cstdlibの中にrand()という関数が宣言されており、それゆえこれをinclude(インクルード、日本語で「含む」とかいう意味ですね)することでこの関数が使えるようになるのです。この点にも注意してください。

 iostreamやcstdlibは、ヘッダーなどといいます。初心者のころは「いろいろなヘッダーの内容を全部覚えなければいけないのかあ」なんて気が滅入ったものですが、実際に使う関数はそれほど多くなく、徐々に必要なものから覚えていけば、どうということはありません。

 上の説明がかえってわかりづらかった人は、とりあえず、coutやcinを使うためには「#include <iostream>」をはじめの方に書き、rand()を使うときには「#include <cstdlib>」を書く、と覚えておけば別に問題ありません。

  さて、わかったような気がしたら、上のプログラムを何度か実行してみてください。実は同じ乱数がでてくることにすぐ気がつくと思います。これはある乱数の系列をいつも同じように出力しているからです。
 ここで、プログラムのはじめに「#include <ctime>」を書き、mainのはじめに「srand( (unsigned)time( NULL ) );」という行をいれると、実行するたびに異なる乱数が発生するようになります(ただし、実行の間隔を少しだけあける必要があります)。このsrandの行の働きを「乱数を初期化する」などというのですが、要するにこれで本当にいろいろでたらめな数がでてくるようになるのです。見た目はややこしいですが、要するに、現在の時刻をもとに発生する乱数のもとを決める行なのです。したがって、プログラムを実行する時刻が異なれば、rand()から異なる乱数が得られることになるわけです。
 ただし、「srand( (unsigned)time( NULL ) );」では、「時刻をみる関数time()」も使っています(そういうものなのです)。そのため、関数timeを宣言しているヘッダーctimeをインクルードする必要があるのです。これが「#include <ctime>」です。いやですね、ごたごたして。でもとりあえず、そうすれば乱数が得られるのです。そうしましょう。ちゃんと書くと

//ransu2.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main()
{
    srand( (unsigned)time( NULL ) );

    cout << rand() << endl;
    cout << rand() << endl;
    cout << rand() << endl;
}

というプログラムになります。
 例によって、説明は長かったですが、コードはransu.cppより2行増えただけです。たったこれだけなのです。これで、いつも違う数がでてくるはずです。何度か実行してみてください。

Fig.2 ransu2.exeの実行

 これで、でたらめな整数が得られるわけですが、でたらめすぎるかもしれません。たとえば、「1から10までの間のでたらめな数」が必要ならどうすればよいのでしょうか。それには「%」という演算子を使うことができます。この演算子は乱数に限らずいろいろなところで出てくる演算子です。プログラムをするなら必ず覚えてください。これは

    x = y % z;

と書いてあれば、「yをzで割り、そのときの余りをxに代入せよ」という意味です。以下に例をあげます。
    x = 25 % 5;  //「x = 0;」と同じ
    x = 25 % 3;  //「x = 1;」と同じ
    x = 25 % 7;  //「x = 4;」と同じ
    x = 25 % 50; //「x = 25;」と同じ
 1行目は「25割る5は商(割った答え)5余り0」なのでxには0が入ります。ここで商は捨ててしまうのです。2行目も同様に「25割る3は商8余り1」で、商は捨ててxには1が入るのです。
 ところで、(0か正の)整数をある数で割った余りはその数より必ず小さいという宇宙の法則があります。例えば、7で割った余りは、必ず0から6までのどれかになります。
 この性質は使えます。例えば、rand()はコンピュータの許す限りのある「0以上の整数」なのですが、rand() % 7としてしまえば、それは0から6までのどれかになるわけです。
 それでは、問題です^^)。もし、0から4までのどれかを発生させたければどうします?正解は「rand() % 5」ですね。もし、0からではなく、1から、、、という数がほしければ、1を足せばよいのです。1から3までのでたらめな整数がほしければ、「rand() % 3 + 1」とします。「rand() % 3」が0から2までのある整数ですから、1を足せば、1か2か3のどれかになるわけです。
 それでは、1から10までの乱数を3つ発生させるプログラムを書いてみてください。...答えは、次のようになりますね。

//ransu3.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main()
{
    srand( (unsigned)time( NULL ) );

    cout << rand() % 10 + 1 << endl;
    cout << rand() % 10 + 1 << endl;
    cout << rand() % 10 + 1 << endl;
}

Fig.3 ransu3.exeの実行

 上の説明がめんどうでわかりにくかった人はとりあえず、上のようなプログラムを書けば1から10までのでたらめな数をつくれるということだけ覚えておいてください。
(実は、「乱数」と言っても、コンピュータが発生させるものは完全な「でたらめ」ではないので、「rand() % x」のxが小さいときやかなり大きいときは、あまりでたらめでなくなってしまうこともあります。これを避けるには少し工夫が要りますが、とりあえず、「rand() % x」で様子を見てください。)


 上のプログラムはクラスをつくらないプログラムでした。これはプログラムがとても簡単だからです。
 以下に「おみくじ」のクラスを作って使うプログラムを載せておきます。少し強引にクラスを作った感じはしますが、まずは、頭の体操程度に思って見てみてください。
 コンストラクタでは、ユーザに「自分のラッキーナンバー」を入力してもらって、それをメンバ変数unに格納し、hiku関数では、発生させた乱数とunを比べ、同じときには「大吉」、そうでないときには「並み」と表示するようにしたのです。ちょっとがんばって読んでみてください。よくわからない人は少し復習してみてください。よくわかる人はひとりでにやにやしてみてください。

//omikuji.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

class Omikuji
{
    int un;  //ラッキーナンバー
public:
    Omikuji();   //コンストラクタ
    void hiku();
};

//コンストラクタ、ラッキーナンバーをユーザに代入してもらいます
Omikuji::Omikuji()
{
    srand( (unsigned)time( NULL ) );
    cout<<"自分のラッキーナンバー(1〜5)を入力してください。"<<endl;
    cin>>un;  //自分のラッキーナンバーを代入
}

//おみくじを引く
void Omikuji::hiku()
{
    int x;
    x = rand() % 5 + 1;   //1〜5の乱数を発生させ、xに代入、これが引いたおみくじの番号
    cout << "あなたの運勢は";
    if(x == un){                                            //xとunが等しければ大吉
        cout << "大吉ということです。" << endl;
    }
    else{                                                    //xとunが等しくなければ「普通」
        cout<<"並みということです。"<<endl;
    }
}

int main()
{
    Omikuji today;  //今日のおみくじ
    today.hiku();
}

Fig.4 omikuji.exeの実行画面
(いやあ、これはなかなか大吉にならないおみくじです。
私は大吉を出すのに7回実行しました。)

 ところで、上のプログラムをクラスなしで簡単にかけないでしょうか?答えは「書けます」です。

//omikuji2.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main()
{
    int un;  //ラッキーナンバー
    cout << "自分のラッキーナンバー(1〜5)を入力してください。" << endl;
    cin >> un;  //自分のラッキーナンバーを代入
    srand( (unsigned)time( NULL ) );
    int x;
    x = rand() % 5 + 1;  //引いたおみくじの番号
    cout << "あなたの運勢は";
    if(x == un){                                            //xとunが等しければ大吉
        cout << "大吉ということです。" << endl;
    }
    else{                                                    //xとunが等しくなければ「普通」
        cout<<"並みということです。"<<endl;
    }
}

 いかがでしょう。このように短いプログラムを書く場合、クラスを書かない方が簡単です。というか、タイプ量が少しですむわけです。しかし、一度「おみくじクラス」を作ってしまえば、それをいろいろなプログラムで使えるということを考えてください。たとえば、ゲームプログラムの前フリに使うこともできるでしょう。もちろん、このプログラム自身を大きなものに発展させるときも、クラスを使った方がやりやすいはずです。クラスは「再利用可能なプログラムの部品」なのです。ぜひ、クラスを書くようにしてください。

 ところで、「大吉」と「並み」しかないおみくじって少し変ですね。改良したい人は、if文をもう少し細かく使って、「中吉」とか「小吉」なども入れてみてください。


 前回、魔王と対決するプログラムでは、進行を一時ストップするのに、

    cin.sync(); 
    cin.get();

というものを使いました。
 実は、「cin.get()」は、「キーボードから一文字だけ文字を読み込め」という意味になります。詳しく言うと、これで「ユーザの入力を待ち、ユーザが文字を入力したら、この関数の書かれている場所にその文字を置いてくれる(「その文字を戻す」)」という関数なのです。したがって、cin.getの本来の役目は「読み込み」なのです。しかし、入力があるまで、つまり、エンターキーが押されるまで待ち状態になってくれるため、「プログラムの進行をエンターキーが押されるまで止める」ためにも使えるのです。このような使い方をする場合、「受け取った文字を変数に格納する」などという処理も必要ないでしょう。実際、上のコードで、そのような処理はしていません。
 ただし、getを実行する前に、キーボードから読み込んだものがcinの中にたまっているかもしれません。おかしな話に聞こえるかもしれませんが、キーボードから読み込まれたものが、処理されず、「cinの中にたまっている」ということがあるのです。特に、「cin >> x;」などに対し、ユーザは適当なキーを押したあとにエンターキーを押して、入力を完了するわけですが、「>>」は、最後の「エンターキー(改行)」を余計なものとして、cinの中に残してしまいます。(さらに続けて>>で読み込むなら、残された「改行」は「不要なもの」として捨てられますが、cin.getによる読み込み時には、捨てられないのです。それは、getが1字1字きちんと処理するための関数だからでしょう。)そのため、このような「cinの中の残りもの」を捨ててしまう必要があるのです。それが、「cin.sync(); 」なのです。これは、残っていた余計なもの(特に「改行」)に「cin.get();」が反応しないようにするためなのです。

 これで、前回のプログラム(大魔王とヒーロー)で、わからないところは無くなったはずです。もう一度、見直してください。
 次回はこのプログラムをもう少しゲームっぽくするために必要な、繰り返し(forやwhile)について説明します。


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