第11回 アカデミックスキルII C言語(4) 繰り返しと条件分岐
Table of Contents
1 このページの更新履歴
2 処理の流れを制御する要素
C/C++ をはじめとする多くのプログラム言語では,
- 逐次処理
- 条件分岐
- 繰り返し
の3つの要素を組み合わせて処理の流れを制御する.
基本となるのは,前回の講義で作成した 正方形内に3つのランダムな点を発生させるプログラム
(sample3-4.cpp
):
と,これと四分円を合わせてプロットする下記の gnuplot コマンド:
set xrange [0:1] set yrange [0:1] set size square plot "<sample3-4.o" replot sqrt(1 - x ** 2)
である(前回の講義の最後になって「四分円を描くだけなら媒介変数を用いる必要がない」ことに気づきました…).
以降では, sample3-4.cpp
をベースにして,
- 3個ではなく N 個の点を生成し,その座標を出力するプログラム
- 生成された3個の点のうち, 四分円に含まれる点の数をカウントするプログラム
を作ってみよう.その上で,
- 生成したN個の点のうち,四分円に含まれる点の座標だけを出力するプログラム
- 生成した N個の点のうち,四分円に含まれる点の数をカウントするプログラム
- 上記 4. のプログラムを使って,円周率を推計するプログラム
を作ってみよう.
3 for
文による繰り返しを習得しよう
3.1 正方形の中に N個の ランダムな点をプロットするプログラム(sample4-1.cpp
)
sample3-4.cpp
を修正して「10個」の点を生成・出力させるにはどうしたらよいだろう?
すぐに思いつくのは,15〜20行目:
double x1 = (double) rand() / ( (double)RAND_MAX ); double y1 = (double) rand() / ( (double)RAND_MAX ); double x2 = (double) rand() / ( (double)RAND_MAX ); double y2 = (double) rand() / ( (double)RAND_MAX ); double x3 = (double) rand() / ( (double)RAND_MAX ); double y3 = (double) rand() / ( (double)RAND_MAX );
を点の数だけ増やす方法である.しかし,この方法には,以下の致命的な欠点がある:
- 100個, 1000個, …と生成する点の数につれてソースコードが長くなり,それだけ入力ミスが増える
- 予め決められた個数の点しか生成させられない.
このように同じ処理を何度も行ないたい場合には 繰り返し を使う.C/C++ には,繰り返し用の構文として, 以下の3つが用意されている.
-
for
- 予め決まった回数 だけ処理を繰り返したい場合
-
while
- ある条件が満足されるまで 処理を繰り返したい場合(繰り返しの「前」に条件を判定)
-
do...while
- ある条件が満足されるまで 処理を繰り返したい場合(繰り返しの「後」に条件を判定)
この講義では for
構文のみを解説する.
~/cpp
の下に以下のサンプル・プログラム sample4-1.cpp
を作成・コンパイルし,
sample4-1.o
という実行ファイルを作成しよう.
- Emacs でファイルを開くには
C-x C-f
(file-file)~/cpp/sample4-1.cpp
というファイルを開く/新しく作るには:C-x C-f ~/cpp/sample4-1.cpp RET
- Emacs 上にペーストするには
C-y
(yank) - Emacs でファイルを保存するには
C-x C-s
(save-buffer) - Emacs 上でコンパイル/実行するには
M-!
(shell-command)sample4-1.cpp
をコンパイルしてsample4-1.o
という実行ファイルを作るには:g++ sample4-1.cpp -o sample4-1.o
sample4-1.o
を実行するには./sample4-1.o
実行すると,以下のように,10組の0以上1未満の乱数ペアが表示されるはずだ(出力される値は実行状況によって異なる):
0.527762 0.0991858 0.0156348 0.774582 0.39885 0.475178 0.309051 0.223189 0.137885 0.440539 0.134666 0.32532 0.65007 0.726346 0.696439 0.0479749 0.314471 0.319364 0.559033 0.66221
この出力を gnuplot に渡せば,10個の点が表示される. 「端末」上で
cd ~/cpp
gnuplot
と入力して gnuplot を起動し,
gnuplot>
というプロンプトが出ている状態で,以下を入力する.
set xrange [0:1] set yrange [0:1] set size square plot "<./sample4-1.o"
新しいウィンドウが開いて,10個の点がプロットされているだろうか?
何度か gnuplot>
プロンプトに対して
plot "<./sample4-1.o"
を繰り返し入力してみよう(カーソルキーの上下で過去に入力したコマンドを表示させられる).
3.2 sample4-1.cpp
の解説
15行目
int N = 10; // 繰り返し回数
では,繰り返し回数を整数(int
)型変数 N
に格納している.
繰り返し回数を for
文(後述)の中に直接書くこともできるが,
こうして意味のある名前を持たせた変数に格納しておくことが
「読み易さ」および「保守のしやすさ」の観点から望ましい.
16〜24行目では for
文を用いた繰り返し処理が記述されている.
for
構文は以下のような構造をしている.
for (センテンス1; センテンス2; センテンス3) // for文の宣言 { // for ブロックの始まり センテンス4; センテンス5; : : } // for ブロックの終わり
ここで, for(...)
の括弧内の3つのセンテンスは,それぞれ,以下の役割を担っている:
- センテンス1: 初期処理
- 繰り返しを始める前(最初の処理が行なわれる前)に実行される処理.
- センテンス2: 条件判定
- 真(true, 0以外の値)もしくは偽(false, 0)のいずれかを取る.真である限り,繰り返しが継続される.通常「関係演算子」が用いられる.
- センテンス3: 挿入処理
- 繰り返しを行う度(1つの処理が終わり,次の新しい処理が行なわれる前)に実行される処理.しばしば「増分演算子」が用いられる.
一般に, for
文は「ブロック内の処理を N
回繰り返す」ときに用いられる.
この場合,一般的には for
文は以下のように書く:
for (int i = 0; i < N; ++i) { 繰り返したい処理 }
;
で区切られた順に説明していこう.
-
int i = 0
(初期処理) - 反復回数を格納する整数(
int
)型変数としてi
を定義し,その値を 0 で初期化する -
i < N
(条件判定) i < N
である間繰り返す.-
++i
(挿入処理) - 繰り返しのたびに
i
の値を1つづつ増やす.++i
はi = i + 1
と同じ意味.
すなわち, この for
文は「 i=0
を初期値とし, i
の値を 1, 2, 3, … と増やしながら i<N
である限り処理を繰り返せ」と指示しているのである.
3.3 関係演算子
「条件判定」のセンテンスには,通常,2つの値を比較する関係演算子が用いられる. 関係演算子は,
値1 演算子 値2
のように使われる.これが評価されると,値1 と 値2 の関係によって真(1
)もしくは偽(0
)のいずれかの値を返す.
関係演算子には,以下のものがある.
関係演算子 | 使い方 | 真を返す条件 |
---|---|---|
< |
a < b |
a が b より小さい |
<= |
a <= b |
a が b と同じかそれより小さい |
> |
a > b |
a が b より大きい |
>= |
a >= b |
a が b と同じかそれより大きい |
== |
== |
a が b と同じ |
!= |
a != b |
a が b と異なる |
「xは"1以上5未満"または"7"」のように複数の条件を組み合わせたい場合には,条件式同士を接続する「論理演算子」を用いる.
論理演算子 | 使い方 | 真を返す条件 |
---|---|---|
&& (論理積) |
A && B |
条件式 A と B の どちらも が真である |
|| (論理和) |
A || B |
条件式 A もしくは B の 少なくともどちらか一方 が真である |
! (否定) |
!A |
条件式 A が 偽である |
本講義では詳しく述べないが,関係演算子および論理演算子を使いこなすためには,以下のルールを知っておくとよい.
- 論理演算子には加減乗除のように処理順序が決まっている(論理積>論理和).
- 処理順序を制御したい場合には,括弧を使って階層化する(深いレベルの括弧から先に処理される).
3.4 練習問題
- 10個の点ではなく, 100個, 1,000個, 10,000個の点をプロットしてみよう.
- 反復回数(iteration)を格納する変数名を
i
ではなくitr
にしてみよう. - 繰り返し回数を
N+1
回にするには,どこをどう変えたらよいだろう?
4 if..else...
文による条件分岐を習得しよう
4.1 ランダムに点を生成し,それが四分円に含まれているか否かを判定するプログラム(sample4-2.cpp
)
~/cpp
ディレクトリの下に以下のサンプル・プログラム sample4-2.cpp
を作成し,
sample4-2.o
という実行ファイル名でコンパイルしよう.
これを何度か実行すると,以下のような出力が得られるはずだ(出力内容は実行状況によって異なる).
$ ./sample4-2.o x: 0.768915 y: 0.16269 >>> 四分円に含まれます <<< $ ./sample4-2.o x: 0.768915 y: 0.16269 >>> 四分円に含まれます <<< $ ./sample4-2.o x: 0.987875 y: 0.209734 < 四分円に含まれません > $ ./sample4-2.o x: 0.206834 y: 0.256779 >>> 四分円に含まれます <<< $ ./sample4-2.o x: 0.425793 y: 0.303824 >>> 四分円に含まれます <<< : :
4.2 sample4-2.cpp
の解説
21〜29行目
// 四分円に含まれるか否かを判定し,表示する if ( x*x + y*y <= 1.0 ) // if 文の条件を記述(x^2+y^2<=1.0 なら真,そうでなければ偽) { // 条件が真の場合のブロックの始まり cout << ">>> 四分円に含まれます <<<" << endl; } // 条件が真の場合のブロックの終わり else // 条件が満足されなかった(偽の場合)場合のブロックが続くことを宣言 { // 条件が偽の場合のブロックの始まり cout << "< 四分円に含まれません >" << endl; } // 条件が偽の場合のブロックの終わり
では, if...then
構文を用いて, ランダムに生成された点 (x, y) が
- 点(x, y) が四分円に含まれる(
x*x + y*y <= 1.0
が真の場合) - 点(x, y) が四分円に含まれない場合(
x*x + y*y <= 1.0
が偽の場合)
のいずれに該当するかを判別し,その後の処理を分岐させている.
まず,21〜24行目
if ( x*x + y*y <= 1.0 ) // if 文の条件を記述(x^2+y^2<=1.0 なら真,そうでなければ偽) { // 条件が真の場合のブロックの始まり cout << ">>> 四分円に含まれます <<<" << endl; } // 条件が真の場合のブロックの終わり
では, 関係演算子を用いた式 (x*x + y*y <= 1.0)
を評価し,
これが真(点が四分円に含まれる)である場合には,その後のブロックを実行することを指示している.
次に,25〜29行目
else // 条件が満足されなかった(偽の場合)場合のブロックが続くことを宣言 { // 条件が偽の場合のブロックの始まり cout << "< 四分円に含まれません >" << endl; } // 条件が偽の場合のブロックの終わり
では, if
文に与えられた条件 (x*x + y*y <= 1.0)
が満足されなかった場合には,
それに続くブロックを実行することを指示している.
if
構文は,基本的に以下の構造を持つ:
if ( 式 ) // if 文の条件を記述(x^2+y^2<=1.0 なら真,そうでなければ偽) { // 条件が真の場合のブロックの始まり 真の場合の処理 } // 条件が真の場合のブロックの終わり else // 条件が満足されなかった(偽の場合)場合のブロックが続くことを宣言 { // 条件が偽の場合のブロックの始まり 偽の場合の処理 } // 条件が偽の場合のブロックの終わり
if
の直後の括弧内には,評価された際に真(0以外)が偽(0)のいずれかを返すセンテンスが与えられる.
一般に用いられるのは,これは変数どうし,もしくは変数と定数とを,関係演算子を用いて比較する式である.
sample4-2.cpp
のケースでは, x*x + y*y
という項と 1.0
という定数を,関係演算子 <=
で比較している.
なお, 偽の場合に何も処理を行なわない場合, else
以降を省略できる.
4.3 練習問題
- ランダムな点が正方形のどの場所にも等確率で生成されるとしたとき,その点が四分円に含まれる確率を考えてみよう
./sample4-2.o
を何度か繰り返してみて,四分円に含まれる回数が上で求めた確率に近いかどうか確認してみよう
4.4 3つの点のうち四分円に含まれる個数をカウントするプログラム(sample4-3.cpp
)
上記の練習問題の2つ目をもう少しだけシステマティックに行うプログラムを作成しよう.
~/cpp
ディレクトリの下に,下記の内容のサンプル・プログラム sample4-3.cpp
を作成し,
sample4-3.o
という名前でコンパイル・実行してみよう.
何度か実行してみると,以下のような出力が得られるはずだ(実際の値は実行時の状況によって異なる):
$ ./sample4-3.o 0.136003 0.801979 0.865367 0.229632 0.431412 0.73584 四分円に含まれた点の数は 3個です $ ./sample4-3.o 0.573921 0.896068 0.223097 0.588225 0.300797 0.502844 四分円に含まれた点の数は 2個です $ ./sample4-3.o 0.792881 0.943113 0.901962 0.267522 0.23549 0.886346 四分円に含まれた点の数は 2個です $ ./sample4-3.o 0.230799 0.0372023 0.259691 0.626114 0.104876 0.653351 四分円に含まれた点の数は 3個です $ ./sample4-3.o 0.449758 0.084247 0.938556 0.305411 0.039569 0.0368527 四分円に含まれた点の数は 3個です $ ./sample4-3.o 0.668717 0.131292 0.61742 0.984707 0.974262 0.420355 四分円に含まれた点の数は 1個です
4.5 sample4-3.cpp
の解説
28行目
int n = 0; // 四分円に含まれる点の数 (0 で初期化)
四分円に含まれる個数のカウント用に n
という名前の整数(int
)型変数を宣言し,
0で初期化する.続いて,29〜32行目:
if ( x1*x1 + y1*y1 <= 1.0) // (x1, y1) が四分円に含まれるなら n を1つ増やす { ++n; }
で, (x1, y1) が四分円に含まれていたら n
の値を1つ増やす.
なお,四分円に含まれない場合には何の処理も行なわないため, else
ブロックが省略されている.
同様に, 33〜40行目
if ( x2*x2 + y2*y2 <= 1.0) // (x2, y2) が四分円に含まれるなら n を1つ増やす { ++n; } if ( x3*x3 + y3*y3 <= 1.0) // (x3, y3) が四分円に含まれるなら n を1つ増やす { ++n; }
では,(x2, y2) および (x3, y3) のそれぞれについて,四分円に含まれていたら n
の値を1つ増やす,という処理を行なっている.
4.6 練習問題
- ランダムな点が正方形内のどの地点にも等確率で生成されるとした場合, 3つの点のうち,四分円に含まれる数が
- 0個の場合
- 1個の場合
- 2個の場合
- 3個の場合
の確率は,それぞれ,いくらになるだろうか.
./sample4-3.o
を何度か実行してみた場合,上述の確率と同じような頻度が実現するだろうか.
5 挑戦してみよう
5.1 正方形内に N
個の点を生成し,その中で四分円に含まれる点のみを出力するプログラムを作り, gnuplot でプロットしてみよう
- 最初は
N=100
くらいから試してみよう - 「(0.5, 0.5) を中心とする半径 0.5 の円に含まれる点のみプロット」にも挑戦してみよう
5.2 正方形内に N
個の点を生成し,その中で四分円に含まれる点の個数をカウントし,円周率を推計するプログラムを作ってみよう
- 点が正方形内のどの場所にも等確率で生成される場合,
N
を充分に大きくすれば, 生成した全ての点の数N
に対する四分円に含まれる点の数n
の比率を4倍したもの4.0 * (double)n / (double) N
は 円周率に等しくなるはずである.N
をいくつか変えてみて,これを確かめよ. cout << 4.0 * (double) n / (double) N = として得られる出力で,表示精度(小数点以下の表示桁数)が不足する場合は, =<iomanip>
ライブラリに含まれているsetprecision()
関数を用いることで, 小数点以下の表示桁数(の最大値)を指定できる.main
関数を呼び出す前に#include <iomanip>
と
<iomanip>
ライブラリを読み込んでおく.そして,表示させたい数値の直前で, 出力ストリームにstd::setprecision(表示桁数)
を渡すことで,表示精度を変えられる.例えば,小数点以下第9位までを表示させたい場合には,
cout << setprecision(9) << 4.0 * (double) n / (double) N << endl;
などとする (
using namespace std
を宣言しているので,std::
は省略できる).なお,表示対象自体の精度以下の桁は表示されない.例えば,
N=1000
の場合4*n/N
の精度は たかだか 小数点以下3位なので,4位以下は表示されない. どうしても指定した桁まで表示させたい場合には, 以下のように出力ストリームにstd::fixed
を渡しておけばよい.cout << fixed << setprecision(9) << 4.0 * (double) n / (double) N << endl;