アドプロ 2016.04.28

制御構造 (1)

前回,基本的なプログラムによって,とりあえずタートルを動かしてみた. その結果,命令(制御関数)のひとつひとつに反応して, タートルが状態を変化させることがわかったハズである.

様々な命令をたくさん羅列すれば, どんなに複雑な図形でも描くことができる. しかし,同じ命令を何度も羅列するのは高コストである. (プログラミング作業に時間がかかるし,訂正・改造も大変になる.) そこで,本授業では,より効率的な(複雑な図形をより少ない命令で描く) 方法を探って行く.

本授業では,効率化の具体的な方策として, 制御構造と関数とを利用する.

制御構造には, 連接(羅列,順次), 選択(条件判断), 反復(繰り返し)の3種類がある.

今回は,反復(繰り返し,ループ)による効率化を理解しよう. 例えば,ある動作を 100 回繰り返す場合, その動作に対応する一連の命令を人間が 100 回も書く のは明らかに無駄である. 効率的なプログラミングとしては, その一連の命令を 1 回だけ書き, 繰り返し回数を指定するだけでよい.

要するに,人間が命令を何度も繰り返すのではなく,コンピュータに繰り返させる.

C言語の場合, for 文または while 文を利用して, 反復処理を記述する.

作業に移る前に,今回も, 作業用ディレクトリの準備をお忘れなく. また,C言語プログラムのコンパイルと実行の方法について, 前回の説明を再確認しておこう.


非効率な羅列的プログラミング

例として,多角形の描画を考える. まずは,6角形を羅列的にプログラミングしてみよう. 一定距離の前進と 60 度の回転を6回実行すれば6角形になる.

ソースファイル polygon.c効率の悪い例

#include "kame3d.h"

int main()
{
	Init("Kame3D Polygon");

	Goto(-1.0, -1.732);

	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);

	Play();
	return (0);
}

この例では, まったく同じコードMove(2.0); Turn(60.0);」を 6回も羅列している.

「たったの6回だけ」ではない. 「仏の顔も3度まで」だ. 人間の忍耐(羅列)の限度は,3回程度.

また,このコードを改造し頂点(角)の個数を増やせば,円に近づけることも可能である. しかし,そのためにソースコードをどれだけ肥大化させなければならないだろうか?

もちろん,現代の PC では, コピー&ペーストの手作業の繰り返しによって, 何行でも簡単に羅列できるので,そうしたくなるのが人情だが, それをやってはいけない. 後で,変更したいとき等,余計な時間・労力がかかることになるし, 不注意による間違いも混入しやすくなってしまう. 人間が働くのではなく, コンピュータを働かせること. 人間は手よりも,頭の方を多く使うべきだ.


反復による効率化

では,次の通り, for を利用して, polygon.c を書き換えよう:効率の良い例

#include "kame3d.h"

int main()
{
	int  i;				// 反復回数のカウンタ

	Init("Kame3D Polygon");

	Goto(-1.0, -1.732);

	for (i = 0; i < 6; i++) {	// ここから6回反復
		Move(2.0); Turn(60.0);
	}				// ここまで

	Play();
	return (0);
}

実行結果は最初のものと変わらないのに, ソースコードを非常にコンパクトに記述できた. また,その効果は,反復回数が多いほど,より強力になる. 反復回数を変えて試してみよう.

回数に応じて,当然,回転角度も変える必要がある. 回数が n の場合,回転角度を 360.0/n にすればよい. (注意:整数演算 360/n だと,割り切れる場合を除き, 計算結果が不正確となる.) 例えば:
int n = 12;	// この値を変えるだけで任意の多角形を描ける
...
for (...; i < n; ...) {
	...
	Turn(360.0/n);
}

なお,for ループの標準的な使い方は次の通り:

int  カウンタ;

for (カウンタ = 0; カウンタ < 反復回数; カウンタ++) {
	繰り返したい処理
	...
}

このループを実行すると, カウンタの値が 0, 1, 2, ..., 反復回数-1 まで増加しながら, ループ内の処理(命令のグループ)が反復される. カウンタの値が反復回数以上になったとき, 反復は終了する.

なお,for ではなく,while を使っても同様な処理が可能: (だが,オススメできない.

int  カウンタ = 0;

while (カウンタ < 反復回数)  {
	繰り返したい処理
	...
	カウンタ++;
}

for と while の使い分けの基本方針:


練習問題

polygon.c を元にして, 反復処理で何か素敵な図形を描け. (前回作成した図形を複数個ならべてみるとか.)

ヒント: for ループを二重・三重にしたり, ループ内に色々な命令を詰め込んだり, 移動距離や回転角度をカウンタ値から計算したりすると, 面白い図形ができあがる. どんな命令(制御関数)があったか, 前回の説明を再確認しよう.

サンプルソースファイル:

これらのソースファイルについては, 本日の作業用ディレクトリにダウンロードして使うとよい.

ダウンロード方法:

リンクを右クリック→ [名前を付けてリンク先を保存...]

ファイルダウンロードを賢く活用するための firefox の設定:
[編集]→[設定]→[一般]→v ファイルごとに保存先を指定する
(標準では,保存先フォルダが「ダウンロード」とかに設定にされているかもしれない.)
なお,制御構造(連接,選択,反復)の考え方を理解するには, アルゴロジック で練習するとよいだろう. (演習室では利用できないようなので,自宅や研究室で.)

規則性・類似性による効率化

羅列的な悪い例:

	Move(0.2); Turn(30.0);
	Move(0.4); Turn(30.0);
	Move(0.6); Turn(30.0);
	...
	Move(2.0); Turn(30.0);

これは,「まったく同じ」ではないが, 「同じような」コードの羅列だ.

改善案 (1):

	int	i;

	for (i = 1; i <= 10; i++) {
		Move(i*0.2);		// 異なる部分を規則性によりコンパクトに表現
		Turn(30.0);
	}

改善案 (2):

	int	i;
	double	d[10] = {0.2, 0.4, 0.6, ..., 2.0};	// 異なる部分だけを羅列

	for (i = 0; i < 10; i++) {
		Move(d[i]);
		Turn(30.0);
	}

本日の課題

for 文を利用して, 規則性のある図案を効率良く描くような タートルグラフィックスプログラムを自由に作成せよ.

担当教員へレポートを送信せよ:


(c) 2016, yanagawa@kushiro-ct.ac.jp