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の実行画面
(実行するたびに結果が違う)
これで、でたらめな数を得ることできるようになりました。しかし、でたらめすぎるかもしれません。たとえば、「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から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文だけで答を書いてみるとプログラムが長くなって、わかりにくくなったのではないでしょうか。あれも、配列の方法を使えば、より簡潔に書くことができます。これは、やや慣れてきた人向けの課題ということにしましょう。)