C++言語講座 第1章 サンプルプログラム2 (占い)
今回はもうちょっと本格的なプログラムを作ります。テーマは「占い」です。 プログラムの内容
プログラムの処理は以下の通り
- 生まれた月日を入力してもらう。
- 生まれた月日と星座と運勢を表示。
- 「Q」を入力したら終了、それ以外の時は(1)に戻る。
という感じです。 使う関数
まず、このプログラムの中で使用する、C++標準ライブラリの関数を紹介します。
rand関数
運勢を表示するわけですが、そんなもの分かるわけが無いので、適当な運勢をランダムで表示します。このように、ランダムな値を得たい場合、rand関数を使うのでしたね。
例えば、0~5の間の乱数が欲しかったら、rand()の返値を6で割った余りを使えばOKです。
rand関数を使うには cstdlib をインクルードします。
srand関数とtime関数
rand()関数を使う前には、main() 関数の頭で
srand(time(NULL));
と実行するのでしたね。今回はこれについて少し詳しく説明したいと思います。
rand関数が返す乱数は、前も説明したように、あくまで「擬似乱数」といって、ある値を元に計算した結果に過ぎません。つまり、その「ある値」が同じならば、毎回同じ値が返ってしまうという事です。これを防ぐために、rand関数を使う前に「srand関数」を使って、その「ある値」を与えてやります。
つまり、実は、
srand(time(NULL));
という行は、srand() 関数に「ある値」として、time(NULL) を渡していることになります。では、この time(NULL) とは一体なんでしょう?
time() は、C++標準ライブラリに含まれている関数です。この関数は、現在の時刻を教えてくれます。……というと、「何時何分何秒」という時間を教えてくれるように聞こえますが、違います。time() 関数は、(大抵の環境では)「1970年1月 1日午前0時0分0秒から数えて何秒たったか」を整数として返してくれるのです。この値を srand() に渡す「ある値」として使えば、プログラムを実行する毎に、バラバラな乱数を rand() が返してくれることになって、幸せになれます。
では、time() 関数に渡している NULL とはなんでしょう? 残念ながら今まで扱ったした範囲内では、まだ説明できません。とりあえず、「こう書く」と覚えてしまって下さい。NULL の意味はまた後で説明します。
まとめると、こういうことになります。
srand(time(NULL));
は、
- time() 関数に NULL を引数として渡して実行した返値を、srand() 関数に引数として渡し実行している
わけです。OKですね?
srand() や time() 関数を使うには、rand() 関数と同じく cstdlib をインクルードします。
プログラム本体
いよいよプログラム本体です。
[List 1]
1 #include <iostream>
2 #include <string>
3 #include <cstdlib>
4 using namespace std;
5
6 // 生まれた月を所得する
7 int get_month()
8 {
9 int ret; // 生まれた月を格納
10
11 // 正しい値を入力するまで繰り返す
12 do {
13 cout << "生まれた月を入力して下さい:";
14 cin >> ret;
15 } while (ret < 1 || 12 < ret);
16
17 return ret;
18 }
19
20 // 生まれた日を所得する
21 int get_day()
22 {
23 int ret; // 生まれた日を格納
24
25 // 正しい値を入力するまで繰り返す
26 do {
27 cout << "生まれた日を入力して下さい:";
28 cin >> ret;
29 } while (ret < 1 || 31 < ret);
30
31 return ret;
32 }
33
34 // 生まれた日にちから星座を計算する
35 string get_constell(int month, int day)
36 {
37 string ret; // 星座の名前を格納
38
39 if ( (month == 3 && day >= 21) || (month == 4 && day <= 20) )
40 ret = "牡羊座";
41 if ( (month == 4 && day >= 21) || (month == 5 && day <= 21) )
42 ret = "牡牛座";
43 if ( (month == 5 && day >= 22) || (month == 6 && day <= 21) )
44 ret = "双子座";
45 if ( (month == 6 && day >= 22) || (month == 7 && day <= 23) )
46 ret = "蟹座";
47 if ( (month == 7 && day >= 24) || (month == 8 && day <= 23) )
48 ret = "しし座";
49 if ( (month == 8 && day >= 24) || (month == 9 && day <= 23) )
50 ret = "乙女座";
51 if ( (month == 9 && day >= 24) || (month == 10 && day <= 23) )
52 ret = "天秤座";
53 if ( (month == 10 && day >= 24) || (month == 11 && day <= 22) )
54 ret = "蠍座";
55 if ( (month == 11 && day >= 23) || (month == 12 && day <= 22) )
56 ret = "射手座";
57 if ( (month == 12 && day >= 23) || (month == 1 && day <= 20) )
58 ret = "山羊座";
59 if ( (month == 1 && day >= 21) || (month == 2 && day <= 19) )
60 ret = "水瓶座";
61 if ( (month == 2 && day >= 20) || (month == 3 && day <= 20) )
62 ret = "魚座";
63
64 return ret;
65 }
66
67 // 結果を表示する
68 void show_result(int month, int day, string constell)
69 {
70 cout << "生まれた月日:"
71 << month << "月" << day << "日" << endl;
72 cout << "星座:" << constell << endl;
73
74 cout << "運勢は…";
75
76 // rand関数の返値によって、運勢を表示
77 int tmp = rand() % 4;
78 if (tmp == 0)
79 cout << "大吉" << endl;
80 else if (tmp == 1)
81 cout << "中吉" << endl;
82 else if (tmp == 2)
83 cout << "小吉" << endl;
84 else
85 cout << "凶" << endl;
86 }
87
88 // 占いを行う
89 void uranai()
90 {
91 int month, day;
92 string constell;
93
94 month = get_month();
95 day = get_day();
96 constell = get_constell(month, day);
97
98 show_result(month, day, constell);
99 }
100
101 // main関数
102 int main()
103 {
104 string str; // ユーザからの入力を格納
105
106 // 乱数を初期化する
107 srand(time(NULL));
108
109 // Q が入力されるまで、繰り返す
110 do {
111 uranai();
112
113 cout << "終了するときは Q を、"
114 << "しない時はそれ以外の文字を入力して下さい:";
115 cin >> str;
116 } while (str != "Q");
117
118 return 0;
119 }
うまく動きましたか?
テクニック
では、ちょっとしたテクニックを紹介しましょう。show_result() 関数は以下のように書き変えることが出来ます。
1 void show_result(int month, int day, string constell)
2 {
3 cout << "生まれた月日:"
4 << month << "月" << day << "日" << endl;
5 cout << "星座:" << constell << endl;
6
7 cout << "運勢は…";
8
9 // rand関数の返値によって、運勢を表示
10 string str[4] = {"大吉", "中吉", "小吉", "凶"};
11
12 cout << str[rand() % 4] << endl;
13 }
これでも同じ結果になります。何故そうなるか考えてみましょう。
同じような方法で、get_constell関数も簡単になりそうです。実際にやってみましょう。
1 string get_constell(int month, int day)
2 {
3 // 星座の名前を月順に格納した配列
4 string constell[12] = {"山羊座", "水瓶座", "魚座", "牡羊座",
5 "牡牛座", "双子座", "蟹座", "しし座",
6 "天秤座", "蠍座", "射手座"};
7
8 // それぞれの月で次の星座が始まる日
9 int start_day[12] = {21, 20, 21, 21, 22, 22,
10 24, 24, 24, 24, 23, 23};
11
12 month--; // 配列の添字に合わせ、monthを0~11の範囲にする
13
14 // 次の星座が始まる日以降なら、monthに1を足す。
15 // 13月は無いので、12で割った余りを代入する
16 if (day >= start_day[month])
17 month = (month + 1) % 12;
18
19 return constell[month];
20 }
プログラムを組む上での指針
皆さんが実際にプログラムを組むときには、どのようなことに気をつけたら良いでしょう。
細かいことは色々有りますが、一番大切なのは「他人が読んで分かるプログラムを書く」という事です。自分以外の人には解読できないようなプログラムは、 1年後には「自分も解読できないプログラム」に変わってしまいます。では、これを実現するためにはどうすれば良いでしょう。
コメントを書く
プログラムの冒頭にはプログラム全体の説明、関数の宣言の前には関数の説明、ある程度まとまった処理の前にはその処理の説明…、というようにプログラムの端々に繁雑にならない限りコメントを書いていきましょう。
なるべく細かく関数に分ける
もし、main() 関数が何百行もあったりしたら、1年後に見たとき、何をしているか即座に分かるでしょうか? 多分分からないと思います。ある程度まとまった処理を書くときは、なるべく関数に分けましょう。
似たような処理はまとめる
同じ処理や、似たような処理が何度も出てきたとします。もし、何処かにバグが有ったり、機能拡張をしたくなったらどうなるでしょう? たった一ヶ所を直したいだけなのに、何百行も書き変えないといけない事になってしまいます。このようなことを防ぐために、似たような処理があったら、関数にしたり、ループを使ったりして、なるべくまとめるようにしましょう。
分かりやすい名前をつける
講座の中では、オブジェクトの名前に x 等を使うことがありましたが、あまり良いことではありません。パッと見で大体、何に使うオブジェクトや関数なのか分かるような名前をつけるのが良いでしょう。このとき、英語の名前をつけた方が英語の勉強にもなって一石二鳥ですよ。(
