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

 今回は、乱数を使ったプログラムを見てみます。
 これを使えば、ちょっとしたゲームが作れるようになります。


 printfやscanfなど、何かしてくれるものを関数というのでした。Cには、乱数を発生させてくれるrandという関数があります。乱数の厳密な定義は難しいのですが、要するに、「でたらめな数」ということです。

 とりあえずサンプルを見てみましょう。それから、説明を読んでください。

/* ransu.c */
#include <stdio.h>
#include <stdlib.h>  /* 後で説明 */

int main(void)
{
    printf("%d\n", rand());
    printf("%d\n", rand());
    printf("%d\n", rand());
    return 0;
}

 実行すると、次のようになります。

Fig.1 ransu.exeの実行画面

 このrandという関数に関する情報は、(どこかにある)stdlib.hというファイルに書かれています。stdlib.hが、実際に、どこにあるかは気にしなくてかまいません。ただ、プログラムのはじめの方に、

#include <stdlib.h>

と書けば、このファイルが読み込まれ、プログラム中でrandが使えるようになるのです。上のプログラムでもそう書いてありますね。
 「rand()」とあるのは、randという関数の実行です。この関数は、実行すると、「rand()が書かれている場所に乱数(ここでは「0以上の整数」)を置いてくれる」ものなのです。
(ただし、「置いてくれる」は業界用語ではありません。Cの用語では、「randは乱数を戻す」とか「randは乱数を返す」といいます。しかし、その意味は、「その場所に乱数を置く」というようなことなのです。用語についてはまた後で説明します。)
 そして、rand()の位置に置かれた乱数が、printfの「"%d"」のところに入れられ、画面に出力されるのです。Fig.1では、1つ目のprintfの出力が130、次が10982で、最後のprintfの出力が1090です。どうです。でたらめな数でしょう?(ただし、環境によって出力される数は異なると思います。Fig.1はあくまで私の環境での話です。)
 しかし、このプログラムは、何度実行しても、出力が「130、10982、1090」となります。つまり、randは「でたらめな数をいつも決まった順序で発生させてくれる関数」なのです。

 これでも十分役に立つのですが、「実行するたびにどうなるかわからない乱数」がほしくなったどうしましょう。その場合は、randを実行する前に、1度だけ

srand( (unsigned)time( NULL ) );

と書けばよいのです。これは、「実行時の時間を元にして、発生させる乱数の元を決める」という程度の意味です。これを書いておけば、発生する乱数が、いつも異なるようになるということです。見た目ややこしいですが、いつもこう書くので、とりあえずこの形で覚えて使えばよいと思います。
 ただし、これを使うためには、プログラムのはじめの方に、

#include <time.h>

を書く必要があります。「time.h」は、上のコードで使われているtimeというものに関する情報が入っているファイルです。(実は、timeは「時間を戻す関数」なのです。が、今はあまり気にしないでください。)
 なお、srandも関数です。しかし、この関数の情報は、randと同様に「stdlib.h」に入っているので、別途インクルード文を増やす必要はありません。
 とにかく、サンプルを見てみましょう。

/* ransu2.c */
#include <stdio.h>
#include <stdlib.h> /* randとsrandを使うため */
#include <time.h>  /* timeを使うため */

int main(void)
{
    srand( (unsigned)time( NULL ) );
    printf("%d\n", rand());
    printf("%d\n", rand());
    printf("%d\n", rand());
    return 0;
}

Fig.2 ransu2.exeの実行画面
(実行するたびに結果が違う)

 なお、srandは、randを実行する前に、1度だけ実行すればよいことに注意してください。
 これで、実行するたびに、(普通は)異なる3つの整数が出力されるようになりました。

 これで、でたらめな数を得ることできるようになりました。しかし、でたらめすぎるかもしれません。たとえば、「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」で、商の8は捨ててxには1が入るのです。以下同様です。
(「商を捨てる」ということがとても気になる人がいるようです。「ほんとに捨てちゃうんですか?」とすがるような目で聞いてくる人がいますが、本当に捨ててしまうのです。
 小学校では、「商」を「答」なんていいますね。「25割る5の答は5」みたいに。一方、「余り」とは、いかにも、「いらないもの」みたいです。しかし、「商」も「余り」も同様に重要なのです。そして、「余り」を捨ててしまう計算もあれば、「商」を捨ててしまう計算もあるのです。%は「商」を捨て「余り」を出すもので、立派に役に立つのです。)

 ところで、ある数(正確には「正の整数」)を別の数で割った余りは割った数より必ず小さいという宇宙の法則があります。どんな数であれ、例えば、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から3までの数」になりますよね。
(実は、「乱数」と言っても、コンピュータが発生させるものは完全な「でたらめ」ではないので、「rand() % x」のxが小さいときやかなり大きいときは、あまりでたらめでなくなってしまうこともあります。これを避けるには少し工夫が要りますが、とりあえず、「rand() % x」で様子を見てください。)

 以上をまとめて、たとえば、1から10までの乱数を10個表示するプログラムは次のように書けます。

/* ransu3.c */
#include <stdio.h>
#include <stdlib.h> /* randとsrandを使うため */
#include <time.h>  /* timeを使うため */

int main(void)
{
    int i;
    srand( (unsigned)time( NULL ) );
    for(i = 0; i < 10; i++)
        printf("%d\n", rand() % 10 + 1);
    return 0;
}

Fig.3 1から10までの数をでたらめに10個表示
(シャッフルではないので、出てこない数もある)


 これで、乱数の説明は終わりです。これを使って、おみくじプログラムを書いてみましょう。実行すると、「今日の運勢がわかる」というものです。

/* omikuji.c */
#include <stdio.h>
#include <stdlib.h> /* randとsrandを使うため */
#include <time.h>  /* timeを使うため */

int main(void)
{
    int x;
    srand( (unsigned)time( NULL ) );
    x = rand()%5;
    switch(x){
        case 0:
            printf("今日は大吉ですぜ。\n");
            break;
        case 1:
            printf("今日は吉です。\n");
            break;
        case 2:
            printf("今日は普通かな。\n");
            break;
        case 3:
            printf("今日は、う〜ん、凶。\n");
            break;
        case 4:
            printf("今日は、すまん、大凶だ。\n");
            break;
    }
    return 0;
}

Fig.4 おみくじ
(おみくじのコツは、大吉が出るまで引くことです)

 まず、はじめに、xに0から4のでたらめな数を入れ、その値にしたがって表示を変えるわけです。
(ちょっと工夫すれば、xという変数を使う必要がなくなります。どうすればよいかわかりますか?
mainの中を

    srand( (unsigned)time( NULL ) );
    switch(rand()%5){
    ...
    }

のようにすればよいのです。)

 なお、このように、switch文やif文を使って場合わけをしていくと、だんだん、プログラムが長くなって間違えそうです。そのような場合の対処方法はいろいろあるのですが、まず、配列を使う方法をお見せしておきましょう。上のomikuji.cは、たとえば、次のように書き換えることができるのです。

/* omikuji2.c */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    char* kekka[5] = {"今日は大吉ですぜ。", "今日は吉です。",
                            "今日は普通かな。", "今日は、う〜ん、凶。",
                            "今日は、すまん、大凶だ。"};

    srand( (unsigned)time(NULL) );
    printf("%s\n", kekka[rand() % 5]);
    return 0;
}

 kekkaは、charではなく、char*の配列です。前回の最後のところを少しだけ見直してください。「""」で囲まれた文字列を文字列リテラルといい、これは、「どこかに確保された文字列の先頭のアドレス」を表すのでした。「文字列の先頭のアドレス」とは「先頭の文字のアドレス」なので、それは、「charのポインタ」つまり「char*型の変数」に格納できるのでした。
 今の場合5個の要素を持った「char*の配列」を用意し、それで5つの文字列を指すようにしたのです。つまり、kekka[0]が「今日は大吉ですぜ。」を、kekka[1]が「今日は吉です。」を...kekka[4]が「今日は、すまん、大凶だ。」を指しているわけです。(なお、見やすくするために、途中で、適当に改行を入れています。)
 前回の話がややこしかった人は、ここで「アドレスとは、ポインタとは」ともう一度悩まないでください。要するに、上のように「文字列の配列」(正確には「文字列リテラルを指すポインタの配列」)が作れるということがわかればよいのです。私自身は、そのように入門しました。
 あとは、printfで結果を表示しているだけです。「rand() % 5」は0から4までのどれかの整数になるので、結局、omikuji.cと同じ動作をするプログラムになるわけです。
 このように書けば、おみくじ結果を増やすことも、それほど難しくないと思うのですが、どうでしょうか。
(また、入門4の課題では、「クイズを3題出すプログラムを書く」という問題を出しました。if文・switch文だけで答を書いてみるとプログラムが長くなって、わかりにくくなったのではないでしょうか。あれも、配列の方法を使えば、より簡潔に書くことができます。これは、やや慣れてきた人向けの課題ということにしましょう。)

課題8


C入門目次

前のページ  後のページ