静岡理工科大学 菅沼ホーム 全体目次 演習解答例 付録 索引

第5章 制御文

  1. 5.1 分岐
    1. 5.1.1 if 文
      1. (プログラム例 5.1 ) 円周と面積の計算
      2. (プログラム例 5.2 ) 坪・m2 間の単位変換
      3. (プログラム例 5.3 ) 3 つのデータの最大値と最小値
      4. (プログラム例 5.4 ) データの比較
    2. 5.1.2 switch 文
      1. (プログラム例 5.5 ) データの比較( switch 文)
    3. 5.1.3 goto 文(Javaを除く)
      1. (プログラム例 5.6 ) 入力の繰り返し( goto )
  2. 5.2 繰り返し
    1. 5.2.1 繰り返し文
      1. (プログラム例 5.7 ) 平均値の計算
      2. (プログラム例 5.8 ) ファイル入出力
      3. (プログラム例 5.9 ) for 文のネスト
      4. (プログラム例 5.10 ) 入力の繰り返し( do-while )
      5. (プログラム例 5.11 ) 最大値(初期設定)
    2. 5.2.2 繰り返しの中断
      1. 5.2.2.1 break 文
        1. (プログラム例 5.12 ) 繰り返しの中断( break )
      2. 5.2.2.2 continue 文
        1. (プログラム例 5.13 ) 繰り返しの中断( continue )
  3. 演習問題5

5.1 分岐

  以上見てきたプログラムは,すべて最初から順番に実行するタイプのものだけでした.しかし,プログラムによっては,必ずしも上から順に実行するだけでなく,その実行順序を変化させたい場合があります.そのような目的のために使用される文を制御文と言います.本章では,主な制御文について説明します.

5.1.1 if 文

  if 文は,そのときの状況により実行の順序を変化させるための文であり,その一般的形式は以下の通りです.
	if (論理式) {
		文1(複数の文も可)
	}
	else {
		文2(複数の文も可)
	}
	・・・・・・
		
  この文の実行は以下のようにして行われます.まず,論理式が評価され,その結果が真であれば,文1が実行され,次に,「・・・」以降に書かれた文が実行されます.この場合は,文2は実行されないことになります.また,偽である場合は,文1は実行されず,文2が実行された後,真の場合と同様,「・・・」以降に書かれた文が実行されます.なお,if 文において,真に対応する部分(文1)は必ず必要ですが,偽に対応する部分( else 以下)は,必ずしも必要ありません.

  また,次の例のように,文1や文2の中にも if 文を書くことができます( if 文のネストと呼びます).同様に,その内部に書かれた if 文の中にも,さらに,if 文を書くことも可能です.
	if (論理式1) {
		・・・・・・
		if (論理式2) {
			・・・・・・
		}
		else {
			・・・・・・
		}
		・・・・・・
	}
	else {
		・・・・・・
	}
		
  また,プログラムを読み易くするため,if 文内に含まれる文は,次の例のように,何列か段を下げて(字下げを行って)書くようにして下さい.字下げは,プログラムの詳細を見なくても,その構造がわかるようにするためのものです.if 文の場合,条件が満足された場合どこからどこまでが実行されるのかが,一目でわかるように行います.例えば,下の例の場合,a と b が等しい場合,2 行目から 7 行目が実行され,また,等しくない場合,10 行目と 11 行目が実行されることが一目でわかります.また,13 行目と 14 行目は,if 文に関係なく常に実行されることも明確になります.
01	if (a == b) {
02		max = y;
03		min = z;
04		if (min < 0.0) {
05			min = 0.0;
06			a   = b;
07		}
08	}
09	else {
10		max = s;
11		min = g;
12	}
13	x = 9;
14	y = 10;
		
(プログラム例 5.1 ) 円周と面積の計算 

  このプログラムでは,半径を入力した後,その値の妥当性(正の値であること)をチェックし( 22 行目),正の場合は目的の計算を行い,それ以外の場合は何も行わず終了しています.従って,else に対応する部分がありません.プログラムを使う人と作る人は常に同じであるとは限りません.場合によっては,プログラム作成者が予期しないデータが入力され,とんでもない結果が生じる場合がよく起こります.従って,できる限り,入力データ値等の妥当性をチェックすることをすすめます.

01	/******************************/
02	/* 円周と面積の計算           */
03	/*      coded by Y.Suganuma   */
04	/******************************/
05	#include <stdio.h>
06
07	int main()
08	{
09		double pi, r, enshu, men;
10	/*
11	     πの値
12	*/
13		pi = 3.141593;
14	/*
15	     半径の入力
16	*/
17		printf("円の半径は? ");
18		scanf("%lf", &r);
19	/*
20	     計算と出力
21	*/
22		if (r > 0.0) {
23			enshu = 2.0 * pi * r;
24			men   = pi * r * r;
25			printf("円周=%f   面積=%f\n", enshu, men);
26		}
27
28		return 0;
29	}
		

  if ブロックが出てきましたので,以前述べた型宣言文の位置に対する C と C++ の違いについて述べておきます.変数 enshu や men が使用されているのは,23 行目~ 25 行目の間だけです.従って,C++ の場合,下に示すプログラムのように,23,24 行目において型宣言を行えば十分です.ただし,ここで行った型宣言は,if ブロック( 22 行目~ 26 行目)内の型宣言が行われた以降だけで有効になります.従って,27 行目以降において,変数 enshu や men を参照すると未定義であるというエラーになってしまいます.

  また,23,24 行目の型宣言と共に,コメントに示すように,12 行目で変数 enshu や men の型宣言を行ってもエラーにはなりませんが,これらの変数が未使用であるという警告メッセージが出力されるはずです(この警告メッセージを無視しても結果に影響を与えません).12 行目で行った型宣言は,12 行目以降で有効ですが,if ブロック内で,かつ,23,24 行目の型宣言以降では,その型宣言が有効になるからです(変数 pi に関しては,if ブロック内においても,12 行目の型宣言が有効).27 行目以降において,変数 enshu や men に代入や入力を行う前に,これらの変数を参照すると,初期設置されていないという警告メッセージが出力されます.23,24 行目で代入を行った変数と,12 行目で型宣言を行った変数とは異なる変数だからです.この警告メッセージは無視してはいけません.何らかの処理が必要です.このように,警告メッセージであっても,無視しても問題がない場合と,そうでない場合があることにも注意してください.

01	/******************************/
02	/* 円周と面積の計算           */
03	/*      coded by Y.Suganuma   */
04	/******************************/
05	#include <stdio.h>
06
07	int main()
08	{
09	/*
10	     πの値
11	*/
12		double pi = 3.141593;   // double pi = 3.141593, enshu, men;
13	/*
14	     半径の入力
15	*/
16		double r;
17		printf("円の半径は? ");
18		scanf("%lf", &r);
19	/*
20	     計算と出力
21	*/
22		if (r > 0.0) {
23			double enshu = 2.0 * pi * r;
24			double men   = pi * r * r;
25			printf("円周=%f   面積=%f\n", enshu, men);
26		}
27
28		return 0;
29	}
		

(プログラム例 5.2 ) 坪・m2 間の単位変換 

  次に,面積に関するデータの単位変換をするプログラムについて考えてみます.つまり,与えられたデータが坪単位なら m2 に,m2 単位なら坪に変換するプログラムです.単にデータを与えただけでは,そのデータが坪単位か m2 単位かがわかりませんので,このプログラムでは変数 sw に値を入力することによって識別しています.変数 sw の値が 0 ならば m2 から坪に,1 ならば(実行上は,0 以外であれば)坪から m2 に変換します.なお,C++ の場合は,コメント( // 以降)のような形でも型宣言可能です.

/****************************/
/* 坪と㎡の間の単位変換     */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	double x, y;   // C++ の場合は,double x;
	int sw;
/*
		  データの入力
*/
	printf("変換方向(0:坪→㎡,1:㎡→坪)は? ");
	scanf("%d", &sw);
	printf("変換するデータは? ");
	scanf("%lf", &x);
/*
		  変換と出力
*/
	if (sw == 0) {             /* 坪から㎡ */
		y = 3.3 * x;   // C++ の場合は,double y = 3.3 * x;
		printf("   xは %f ㎡", y);
	}
	else {				   /* ㎡から坪 */
		y = x / 3.3;   // C++ の場合は,double y = x / 3.3;
		printf("   xは %f 坪", y);
	}

	return 0;
}
		

(プログラム例 5.3 ) 3 つのデータの最大値と最小値 

  次のプログラムでは,与えられた 3 つのデータの最大値と最小値を出力しています.この例により,if 文のネスト構造,及び,字下げの方法を理解して下さい.また,このプログラムの 25 ~ 28 行目等に示すように,if の後や else の後に続く文が 1 文だけである場合は,それらを囲む「{」と「}」は必要ありません(もちろん,付けても構いません).ただし,次のプログラム
	if (式1) {
		if (式2)
			文1;
	}
	else
		文2;
		
	if (式1)
		if (式2)
			文1;
	else
		文2;
		
と書くと,
	if (論理式) {
		文1(複数の文も可)
	}
	else {
		文2(複数の文も可)
	}
		
を 1 文とみなすため,
	if (式1)
		if (式2)
			文1;
		else
			文2;
		
と解釈されてしまいますので気をつけて下さい.なぜなら,コンパイラが,else に対する if は,else に最も近い if と判断するからです.このような紛らわしさを除くためには,多少余分であっても,{ } を付加した方が良いかもしれません.

  また,この最大値と最小値の問題を
	if (a > b && c > a) {
		amax = c;
		amin = b;
	}
	if (a > b && c < b) {
		amax = a;
		amin = c;
	}
	・・・・・
		
のように,else 文を使用しないで書く人がいますが,余分な比較演算を行うことになり,あまり感心できません.また,C++ の場合,変数 amin や amax を if ブロック内で型宣言しても良さそうに見えますが,この例の場合,50 行目でこれらの変数を参照しているため,17 行目以前に行う必要がある点に注意してください.

01	/**************************************/
02	/* 3つのデータの最大値と最小値の計算 */
03	/*      coded by Y.Suganuma           */
04	/**************************************/
05	#include <stdio.h>
06
07	int main()
08	{
09		double a, b, c, amin, amax;
10	/*
11	     データの入力
12	*/
13		printf("3つのデータを入力して下さい ");
14		scanf("%lf %lf %lf", &a, &b, &c);
15	/*
16	     a>bの場合
17	*/
18		if (a > b) {
19			if (c > a) {
20				amax = c;
21				amin = b;
22			}
23			else {
24				amax = a;
25				if (c < b)
26					amin = c;
27				else
28					amin = b;
29			}
30		}
31	/*
32	     a≦bの場合
33	*/
34		else {
35			if (c > b) {
36				amax = c;
37				amin = a;
38			}
39			else {
40				amax = b;
41				if (c < a)
42					amin = c;
43				else
44					amin = a;
45			}
46		}
47	/*
48	     出力
49	*/
50		printf("最大値=%f  最小値=%f\n", amax, amin);
51
52		return 0;
53	}
		

  データの数が増えた場合,上の方法は非常に面倒になります.以下のように書いた方がより一般的であり,また,理解し易いと思います( Java においても同様).

#include <stdio.h>

int main()
{
	double a, b, c, amin, amax;
/*
	 データの入力
*/
	printf("3つのデータを入力して下さい ");
	scanf("%lf %lf %lf", &a, &b, &c);
/*
	 初期設定
*/
	amax = a;
	amin = a;
/*
	 残りの2つのデータと比較する
*/
	if (b > amax)
		amax = b;
	else {
		if (b < amin)
			amin = b;
	}

	if (c > amax)
		amax = c;
	else {
		if (c < amin)
			amin = c;
	}
/*
	 出力
*/
	printf("最大値=%f  最小値=%f\n", amax, amin);

	return 0;
}
		

(プログラム例 5.4 ) データの比較 

  次のプログラムは,入力されたデータを x,y,および,z に代入されている値と比較し,そのどれと等しいかを出力しています.

/****************************/
/* データの比較             */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	int x = 10, y = 20, z = 30;
	int data;
/*
	 データの入力
*/
	printf("データを入力して下さい ");
	scanf("%d", &data);
/*
	 判定と出力
*/
	if (data == x)
		printf("xと等しい\n");
	else {
		if (data == y)
			printf("yと等しい\n");
		else {
			if (data == z)
				printf("zと等しい\n");
			else
				printf("いずれとも等しくない\n");
		}
	}

	return 0;
}
		

  この例ではそれほどではありませんが,一般に,if 文のネストが深くなるとプログラムが見にくくなります.そのような意味で,この例のような場合は,「else if」の構文を利用して,以下のように書いた方がすっきりします.

/****************************/
/* データの比較             */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	int x = 10, y = 20, z = 30;
	int data;
/*
	 データの入力
*/
	printf("データを入力して下さい ");
	scanf("%d", &data);
/*
	 判定と出力
*/
	if (data == x)
		printf("xと等しい\n");
	else if (data == y)
		printf("yと等しい\n");
	else if (data == z)
		printf("zと等しい\n");
	else
		printf("いずれとも等しくない\n");

	return 0;
}
		

5.1.2 switch 文

  switch 文の一般形式は以下の通りです.
	switch (式) {
		[case 定数式1 :]
			[文1]
		[case 定数式2 :]
			[文2]
		 ・・・・・
		[default :]
			[文n]
	}
		
  まず,式が評価されます.その値が定数式の値のいずれかに等しければ,それ以降の文が実行されます.もちろん,文 i は,複数の文でも構いません.いずれの定数式の値にも一致しない場合,もし,default キーワードの項があればそれ以降が実行され,そうでなければ,何も実行されず switch 文以降の文が実行されます.

(プログラム例 5.5 ) データの比較( switch 文) 

  次のプログラムは,入力されたデータを x,y,および,z に代入されている値と比較し,そのどれと等しいかを出力しています.プログラムの中に,break 文が使用されていますが,break 文はその文に至った時点で強制的に switch 文を抜け出すための文です.このプログラムに対して,20 を入力すると,

yと等しい

と出力されますが,もし,break 文が全くない場合は,この出力文以降がすべて実行されるため,

yと等しい
zと等しい
いずれとも等しくない

のすべての文が出力されてしまいます.

  このプログラムのように,if 文を使用して変数 sw に値を代入することなしに,
	switch (data) {
		case x :
			printf("xと等しい\n");
			break;
		case y :
			printf("yと等しい\n");
			break;
		case z :
			printf("zと等しい\n");
			break;
		default :
			printf("いずれとも等しくない\n");
	}
		
のように書ければ良いのですが,case の後には定数式しか許されていないため,不可能です.もちろん,この例の場合には,x,y,および,z に代入されている値を case の後に直接書けば可能ですが,これらの変数に何が代入されているのかが分からないようなときには,採用できない方法です.

/****************************/
/* データの比較             */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	int x = 10, y = 20, z = 30;
	int data, sw;
/*
	 データの入力と判定準備
*/
	printf("データを入力して下さい ");
	scanf("%d", &data);

	if (data == x)
		sw = 0;
	else {
		if (data == y)
			sw = 1;
		else
			sw = (data == z) ? 2 : 3;
	}
/*
	 判定と出力
*/
	switch (sw) {
		case 0 :
			printf("xと等しい\n");
			break;
		case 1 :
			printf("yと等しい\n");
			break;
		case 2 :
			printf("zと等しい\n");
			break;
		default :
			printf("いずれとも等しくない\n");
	}

	return 0;
}
		

5.1.3 goto 文(Javaを除く)

  goto 文は,goto の後ろに書かれたラベルが付けられた箇所に無条件に移動するための文です.ただ,この文を頻繁に使用すると,分かり難いプログラムの原因になります.goto 文を使用した方が読み易いという特別の事情がない限り,使用しない方が良いと思います.goto 文を全く使用しなくても,同等のプログラムは必ず書けるはずです.
	goto ラベル;
	 ・・・
	ラベル: ・・・
	     ・・・
		
(プログラム例 5.6 ) 入力の繰り返し( goto )

  次のプログラムでは,要求にあったデータが入力されるまで,再入力を促しています.また,要求通りのデータの場合は,和を求め出力しています.このプログラムは,次節で述べる繰り返し文を使用すれば,goto 文を使用せずに書くことができます.

/**********************************/
/* 正しいデータの再入力(goto文) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>

int main()
{
	int x,y;

	again: printf("2つの正のデータを入力して下さい ");
		   scanf("%d %d",&x,&y);

	if (x < 0 || y < 0)     /* いずれかが負 */
		goto again;
	else     /* 両方とも 0 以上 */
		printf("和は=%d\n",x+y);

	return 0;
}
		

5.2 繰り返し

5.2.1 繰り返し文

  プログラムによっては,同じ手順を何回も繰り返して行いたいような場合がよく起こります.for 文は,その様なときに使用します.例えば,5 つのデータを読み込みその和を計算する場合,前節までの方法を使用すれば次のようになります.
	#include <stdio.h>
	int main()
	{
		double data;
		double sum = 0.0;
		scanf("%lf",&data);
		sum += data;
		scanf("%lf",&data);
		sum += data;
		scanf("%lf",&data);
		sum += data;
		scanf("%lf",&data);
		sum += data;
		scanf("%lf",&data);
		sum += data;
		printf("和=%f\n",sum);
		return 0;
	}
		
  しかし,5 つ程度のデータであれば,上の方法でも可能ですが,データの数が多くなると困難になります.そこで,このプログラムは,for 文を使用することによって,次のようにより簡単に書くことができます.
#include <stdio.h>
int main()
{
	double data;
	double sum = 0.0;
	int i1;
	for (i1 = 0; i1 < 5; i1++) {
		scanf("%lf",&data);
		sum += data;
	}
	printf("和=%f\n",sum);
	return 0;
}
		
  for 文の一般的形式は以下の通りです.
	for (式1; 式2; 式3) {
		文(複数の文も可)
	}
	 ・・・・・
		
  for 文に入ると,まず,式 1 が実行されます.式 1 は,通常,for 文の繰り返し回数を制御するため等の初期設定を行う式であり,for 文の最初に 1 回だけ実行されます.次に,式 2 (論理式)の値が評価され,もし真であれば文が実行されます.そして,式 3 が実行されます.再び,式 2 が評価され,その値が真である限り,文と式 3 の実行が繰り返されます.式 2 の値が偽になると,文と式 3 は実行されず,「・・・」以下の文が実行されることになります( for 文の外に出る).

  for 文において,式 1 と式 3 を省略することは可能(「;」は省略できない)ですが,通常,式 2 を省略することはできません(省略すると,無限ループになってしまう).

  for 文と同様な機能を持つ文として,while 文do while 文があります.while 文,及び,do-while 文の一般形式は以下の通りです.
	<while文>
		while (式) {
			文(複数の文も可)
		}
		 ・・・・・
	
	<do while文>
		do {
			文(複数の文も可)
		} while (式) ;
		 ・・・・・
		
  while 文では,式の値が真である限り,文の実行が繰り返されることになります.while 文と do while 文の違いは,式の評価が,文を実行する最初に行われるか,または,後で行われるかの違いです.do while 文では,式の評価が後で行われるため,do while 文の開始時に式が偽であっても,文が少なくとも 1 回は実行されることになります.

  先に述べた for 文は,while 文を使用して,次のように書くこともできます.どちらの表現方法を使用するかは趣味の問題ですが,問題に応じて,理解しやすいプログラムになると思われる方を使用して下さい.
	式1;
	while (式2) {
		文(複数の文も可)
		式3;
	}
		
  このように,for 文,while 文,及び,do while 文はほぼ等価ですので,以下の説明では,基本的に,for 文を使用して説明を行います( while 文や do while 文に対しても同様の議論が成立します).

  if 文と同様,for 文の中が 1 文だけの場合は,{ } を省略可能です.また,次の例のように,for 文の中に別の for 文を書くこともできます( for 文のネスト).
	for (式1; 式2; 式3) {
		・・・・・
		for (式4; 式5; 式6) {
			・・・・・
		}
		・・・・・
	}
		
  また,if 文と同じように,プログラムを読み易くするため,for 文内の文は,次の例のように,何列か段を下げて(字下げをして)書くようにして下さい.
	for (i1 = 0; i1 < 10; i1 = i1+1) {
		a = b + c;
		for (i2 = 0; i2 < 5; i2 = i2+1) {
			bcd = a / y;
			aa  = b;
			・・・・・・・・
		}
		sum = c + d;
	}
		
(プログラム例 5.7 ) 平均値の計算 

  以下に,n 人の英語と数学の点数から,それらの平均点を計算するプログラムを,for 文を使用して書いた例を示します.

01	/****************************/
02	/* 平均値の計算             */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include <stdio.h>
06
07	int main()
08	{
09		double mean1, mean2, sum1, sum2, x, y;
10		int n, i1;
11	/*
12	         初期設定
13	*/
14		sum1 = 0.0;
15		sum2 = 0.0;
16	/*
17	         データの数の読み込み
18	*/
19		printf("人数は? ");
20		scanf("%d", &n);
21	/*
22	         データの読み込み
23	*/
24		for (i1 = 0; i1 < n; i1++) {
25			printf("英語と数学の点は? ");
26			scanf("%lf %lf", &x, &y);
27			sum1 += x;
28			sum2 += y;
29		}
30	/*
31	         結果の計算と出力
32	*/
33		if (n <= 0)                    /* 0で割るのを防ぐ */
34			printf("データがない!\n");
35		else {
36			mean1 = sum1 / n;
37			mean2 = sum2 / n;
38			printf("   英語=%f 数学=%f\n", mean1, mean2);
39		}
40
41		return 0;
42	}
		
14,15 行目

  変数 sum1 と sum2 に対する初期設定を行っています.これらの変数は,27 及び 28 行目から明らかなように,各科目の合計点を求めるためのものです.通常,プログラム内で宣言された変数の値は,何らかの形で代入が行われない限り,どのような値になっているかは不明です.もし,これらの変数に 0 以外の値が入っていると,誤った合計計算を行うことになります.そこで,必ず,このような初期設定が必要になります.もちろん,このプログラムの場合は,型宣言文の中で初期設定を行っても構いませんが,常に最初行っておけばよいというものではありません.そのような意味で,多少余分な文を書くことにはなりますが,必要な箇所で代入文によって行うような癖を付けた方がよいと思います.

  また,これらの文は,sum1 = 0 というように書いても正しく実行されます.しかし,変数 sum1 の型は double であるということを常に意識し,誤った型変換等を行わないためにも,0.0 というように,対応する正しい定数表現で記述した方が良いと思います.

24 行目

  この for 文によって,まず,変数 i1 が 0 に初期設定され,25 から 28 行目までの文が,条件「 i1 < n 」が満足される間繰り返されます.25 から 28 行目が実行される毎に,式 i1++ によって,変数 i1 の値が 1 づつ増加しますので,結局,25 から 28 行目の文が n 回繰り返されることになります.

33 行目

  もし,人数 n が 0 以下の場合,36,37 行目で行っている平均を求める操作が無意味なものになりますので,この if 文によってそのような場合を除外しています.特に,n の値が 0 の場合は,0 で割ることになり,プログラムの実行が停止してしまいます.このように,0 で割る可能性があるような場合は,0 でないことをチェックしておいた方がよいと思います.

  C++ の場合,if ブロックの場合と同じく,for ブロック( while 等も同様)内だけで必要な変数は,そのブロック内で定義可能です.下の例において,変数 i1,x,y は,for ブロック( 24 行目~ 30 行目)内だけで有効になります.Java においても,同様の方法が可能であり,プログラム例 5.7 に対応する Java のプログラムはそのような形で記述してあります.ただし,細かな点で,C++ と Java は異なっています.詳細に関しては,変数の有効範囲(スコープ)を参照してください.

01	/****************************/
02	/* 平均値の計算             */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include <stdio.h>
06
07	int main()
08	{
09		double sum1, sum2;
10		int n;
11	/*
12	         初期設定
13	*/
14		sum1 = 0.0;
15		sum2 = 0.0;
16	/*
17	         データの数の読み込み
18	*/
19		printf("人数は? ");
20		scanf("%d", &n);
21	/*
22	         データの読み込み
23	*/
24		for (int i1 = 0; i1 < n; i1++) {
25			double x, y;
26			printf("英語と数学の点は? ");
27			scanf("%lf %lf", &x, &y);
28			sum1 += x;
29			sum2 += y;
30		}
31	/*
32	         結果の計算と出力
33	*/
34		if (n <= 0)                    /* 0で割るのを防ぐ */
35			printf("データがない!\n");
36		else {
37			double mean1 = sum1 / n;
38			double mean2 = sum2 / n;
39			printf("   英語=%f 数学=%f\n", mean1, mean2);
40	}
41	
42		return 0;
43	}
		

  参考のため,上のプログラムを while 文を使用して書き直したものを以下に示します.どちらのプログラムも正しく動作しますが,このように繰り返し回数が明確になっているような場合は,for 文を使用した方が理解しやすいのではないかと思います.

01	/****************************/
02	/* 平均値の計算             */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include <stdio.h>
06
07	int main()
08	{
09		double mean1, mean2, sum1, sum2, x, y;
10		int n, i1;
11	/*
12	         初期設定
13	*/
14		sum1 = 0.0;
15		sum2 = 0.0;
16	/*
17	         データの数の読み込み
18	*/
19		printf("人数は? ");
20		scanf("%d", &n);
21	/*
22	         データの読み込み
23	*/
24		i1 = 0;
25
26		while (i1 < n) {
27			printf("英語と数学の点は? ");
28			scanf("%lf %lf", &x, &y);
29			sum1 += x;
30			sum2 += y;
31			i1++;
32		}
33	/*
34	         結果の計算と出力
35	*/
36		if (n <= 0)
37			printf("データがない!\n");
38		else {
39			mean1 = sum1 / n;
40			mean2 = sum2 / n;
41			printf("   英語=%f 数学=%f\n", mean1, mean2);
42		}
43
44		return 0;
45	}
		

(プログラム例 5.8 ) ファイル入出力 

  上の例では,平均を計算する人数があらかじめ分かっていました.しかし,英語と数学の点がファイルに保存されており,その人数(データの数)が分からないような場合はどのようにしたらよいでしょうか.次のプログラムはそのような場合の例です.今,ファイル「input」に英語と数学の点が,

23 45
34 99
・・・・・

のような形で,任意の数だけ保存されているものとします.そして,結果もファイル「output」に保存するものとします.

01	/************************************/
02	/* ファイル入出力(平均値の計算後) */
03	/*      coded by Y.Suganuma         */
04	/************************************/
05	#include <stdio.h>
06
07	int main()
08	{
09		double mean1, mean2, sum1, sum2, x, y;
10		int n;
11		FILE *in, *out;
12	/*
13	         初期設定
14	*/
15		sum1 = 0.0;
16		sum2 = 0.0;
17		n    = 0;
18	/*
19	         データの読み込み
20	*/
21		in = fopen("input","r");
22
23		while (EOF != fscanf(in,"%lf %lf",&x,&y)) {
24			sum1 += x;
25			sum2 += y;
26			n++;
27		}
28
29		fclose(in);
30	/*
31	         結果の計算と出力
32	*/
33		if (n <= 0)
34			printf("データがない!\n");
35		else {
36			out   = fopen("output", "w");
37			mean1 = sum1 / n;
38			mean2 = sum2 / n;
39			fprintf(out, "人数 %d 英語=%f 数学=%f\n", n, mean1, mean2);
40			fclose(out);
41		}
42
43		return 0;
44	}
		
17 行目

  変数 n はデータの数(人数)を数えるためのカウンタです.一組のデータを読む度に,26 行目において 1 づつ増加させています.そのため,必ず,初期設定が必要です.

21 行目

  fopen は,ファイルから読んだり,ファイルに書いたりするための準備を行う関数です.この行では,ファイル input からの入力の準備を行っています("r"の指定).この関数の左辺にくる変数に対しては,必ず,11 行目に相当する宣言文が必要になります.

23 行目

  ここでは,繰り返し制御のため,while 文を使用しています.この例の場合は,while 文の方が for 文より好ましいと思います.while 文の中の式の意味は,関数 fscanf の値が EOF で無い限り,24 ~ 26 行目の文を繰り返すことを意味しています.EOF とは「End of File」のことであり,関数 fscanf の値が EOF であるということは,「データを読もうとしたが既にデータがなかった」ということを意味しています.従って,この while 文によって,データが存在する間,そのデータを読み込み,24 ~ 26 行目の処理が行われます.

  また,fscanf は,ファイルからデータを読み込むための関数です.カッコの中の最初に fopen で使用した変数が来る以外,基本的に,関数 scanf と同じです.

29 行目

  入力データを読み終え,ファイルを必要としなくなったのでその終了処理を行って(閉じて)います.プログラムの実行が終了すると使用していたファイルはすべて自動的に閉じられるため,このプログラムでは必要性は少ないですが,一般的に,使用し終わったら閉じておいた方が良いと思います.

36, 39, 40 行目

  出力ファイルに対する入力ファイルと同様な処理です("w"の指定).

  参考のため,cin や cout の相当する機能を使用してファイル入出力を行うプログラムの例を書いておきます.行っている内容は,上記のプログラムと全く同じです.

/************************************/
/* ファイル入出力(平均値の計算後) */
/*      coded by Y.Suganuma         */
/************************************/
#include <iostream>
#include <fstream>

int main()
{
	double mean1, mean2, sum1, sum2, x, y;
	int n;
/*
		 初期設定
*/
	sum1 = 0.0;
	sum2 = 0.0;
	n    = 0;
/*
		 データの読み込み
*/
	std::ifstream in("input");

	while (!in.eof()) {
		in >> x >> y;
		sum1 += x;
		sum2 += y;
		n++;
	}

	in.close();
/*
		 結果の計算と出力
*/
	if (n <= 0)
		std::cout << "データがない!\n";
	else {
		std::ofstream out("output");
		mean1 = sum1 / n;
		mean2 = sum2 / n;
		out << "人数 " << n << " 英語=" << mean1 << " 数学=" << mean2 << std::endl;
		out.close();
	}

	return 0;
}
		

(プログラム例 5.9 ) for 文のネスト 

  次に,for 文のネストの例を示します.今,ある学年に,n クラスあったとします.そして,各クラスには m 人(クラス毎に異なる)の生徒がおり,全員に対しある試験を実施したとします.このとき,平均値が最も高いクラス番号( 1 クラスだけであるとします)とその平均値を出力するプログラムは,例えば,以下のようになります.この例により,for 文のネスト,変数の初期設定等を理解して下さい.

01	/************************************/
02	/* クラス平均と最も良いクラスの出力 */
03	/*      coded by Y.Suganuma         */
04	/************************************/
05	#include <stdio.h>
06
07	int main()
08	{
09		double mean, max, x;
10		int i1, i2, max_c, n, m;
11	/*
12	          データの入力と平均値の計算
13	*/
14		printf("クラスの数は? ");
15		scanf("%d", &n);
16
17		for (i1 = 0; i1 < n; i1++) {      /* クラスの数だけ繰り返す */
18
19			printf("%d 番目のクラスの人数は ", i1+1);
20			scanf("%d", &m);
21
22			mean = 0.0;                /* この初期設定はここで必要 */
24
24			for (i2 = 0; i2 < m; i2++) {   /* クラスの人数だけ繰り返す */
25				printf("     %d 番目の人の点は? ", i2+1);
26				scanf("%lf", &x);
27				mean += x;
28			}
29
30			mean /= m;
31
32			if (i1 == 0 || mean > max) {
33				max   = mean;
34				max_c = i1 + 1;
35			}
36		}
37	/*
38	          結果の出力
39	*/
40		printf("最大平均値はクラス %d の %f 点\n", max_c, max);
41
42		return 0;
43	}
		
17 行目

  この for 文により,クラスの数だけ,17 ~ 36 行目までを繰り返します.

22 行目

  変数 mean は,各クラスの平均点を計算するための変数ですので,この位置で初期設定が必要です.型宣言の箇所で初期設定をしても全く無意味です.また,このプログラムは,メッセージレベルの設定にもよりますが,gcc においてコンパイルすると,変数 max および max_c に対して,初期設定がされていないかもしれないという警告メッセージが出力されます.しかし,32 行目からも明らかなように,初期設定を必要としません.しかし,Java においては,エラーとして処理され,このままですとコンパイル不可能です.

24 行目

  この for 文により,各クラスの人数だけ,24 ~ 28 行目までを繰り返します.

32 ~ 35 行目

  変数 i1 の値が 0 ( 1 番目のクラスであることを示す),または,上で計算した変数 mean の値(現在のクラスの平均値)が,変数 max の値より大きいときは,変数 max の値を変数 mean の値で置き換え,かつ,そのクラス番号( i1 + 1 )を変数 max_c に保存します.32 行目において,if 文の中は「 mean > max 」だけで良さそうですが,そのようにすると問題が起こります.なぜなら,変数 max には,当初何が入っているか分からないからです.そこで,最初に計算したクラスに対しては,無条件にそのクラス番号と平均値を保存することがこの if 文の目的です.もちろん,09 行目において,変数 max を負の値で初期設定しておけば,「 mean > max 」だけで十分です.

  C++ を利用して,上のプログラムを書き直したものを下に示します.この例の場合,変数を必要とする箇所で型宣言をする方が,初期設定のミスが少なくなるかもしれません.

/************************************/
/* クラス平均と最も良いクラスの出力 */
/*      coded by Y.Suganuma         */
/************************************/
#include <stdio.h>

int main()
{
	double max;
	int max_c;
/*
          データの入力と平均値の計算
*/
	int n;
	printf("クラスの数は? ");
	scanf("%d", &n);

	for (int i1 = 0; i1 < n; i1++) {      /* クラスの数だけ繰り返す */

		int m;
		printf("%d 番目のクラスの人数は ", i1+1);
		scanf("%d", &m);

		double mean = 0.0;

		for (int i2 = 0; i2 < m; i2++) {   /* クラスの人数だけ繰り返す */
			double x;
			printf("     %d 番目の人の点は? ", i2+1);
			scanf("%lf", &x);
			mean += x;
		}

		mean /= m;

		if (i1 == 0 || mean > max) {
			max   = mean;
			max_c = i1 + 1;
		}
	}
/*
          結果の出力
*/
	printf("最大平均値はクラス %d の %f 点\n", max_c, max);

	return 0;
}
		

(プログラム例 5.10 ) 入力の繰り返し( do while ) 

  プログラム例 5.6 では goto 文を使用して繰り返しの処理を行っていましたが,goto 文を使用せず,do while 文を使用して,次のように書くことができます.もちろん,for 文や while 文を使用して書くことも可能です.少なくとも,このようなケースに対して goto 文を使うべきではないと思います.

/*************************************/
/* 正しいデータの再入力(do while文)*/
/*      coded by Y.Suganuma          */
/*************************************/
#include <stdio.h>

int main()
{
	int x, y;

	do {
		printf("2つの正のデータを入力して下さい ");
		scanf("%d %d", &x, &y);
	} while (x < 0 || y < 0);

	printf("和は=%d\n", x+y);

	return 0;
}
		

(プログラム例 5.11 ) 最大値(初期設定) 

  最大値を求める基本的なアルゴリズムは以下の通りです.まず,最大値を保存する変数,例えば max を適当な値で初期設定しておきます.次に,最大値を求めるデータ群の中の各データと max に代入されている値とを比較し,もし,比較したデータの方が max に代入されている値より大きければ,そのデータを max に代入します.これをすべてのデータに対して繰り返せば最大値が求まることになります.つまり,以下のようにプログラムすればよいわけです(データの数は 5 とし,各データはキーボードから入力されるものとしています).

/****************************/
/* 最大値の計算             */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	int n = 5, i1, max = 0, x;

	for (i1 = 0; i1 < n; i1++) {
		printf("データを入力してください ");
		scanf("%d", &x);
		if (x > max)
			max = x;
	}

	printf("   最大値=%d\n", max);

	return 0;
}
		

  上のプログラムに問題は全くなさそうに見えます.しかし,負のデータだけを入力した場合について考えてみてください.最大値は 0 であるという答えが得られるはずです.明らかに,誤っています.なぜなら,変数 max に初期設定すべき値は,求めるべき最大値より必ず小さくなければならないからです.しかし,求めるべき最大値は不明です.どのようにしたら良いでしょうか.一つの方法は,以下のプログラムのように,最初のデータを max に代入しておく方法です.

int main()
{
	int n = 5, i1, max, x;

	printf("データを入力してください ");
	scanf("%d", &max);		/* 最初のデータでmaxを初期化 */

	for (i1 = 1; i1 < n; i1++) {	   /* i1を1から始める */
		printf("データを入力してください ");
		scanf("%d", &x);
		if (x > max)
			max = x;
	}

	printf("   最大値=%d\n", max);

	return 0;
}
		

  上のプログラムは,どのようなデータを与えても,正しく最大値を出力してくれるはずです.それでは,問題を少し変えて,「与えられたデータの内,負のデータの最大値を求めよ」というようにしたらどうでしょうか.max とデータを比較している部分を,
	if (x < 0 && x > max)
		
のように変えてやればうまくいくように思われるかもしれません.たしかに,最初のデータが負であるときは正しく動きます.しかし,そうでないときは誤った最大値を出力してしまいます.これを回避する一つの方法は,以下のプログラムのように,変数 max が初期化されたか否かを示す指標(変数 sw )を設けてやることです.

int main()
{
	int n = 5, i1, max, sw = 0, x;   /* maxに対する初期設定は不要 */

	for (i1 = 0; i1 < n; i1++) {
		printf("データを入力してください ");
		scanf("%d", &x);
		if (x < 0 && (sw == 0 || x > max)) {
			max = x;
			sw  = 1;
		}
	}

	printf("   最大値=%d\n", max);

	return 0;
}
		

5.2.2 繰り返しの中断

  通常,繰り返し文は,繰り返し条件が成立しなくなるまで続けられますが,場合によっては,繰り返しの途中でループの外へ出たい場合があります.この節では,そのような場合に使用する文について解説します.

5.2.2.1 break 文

  break 文は,break を囲んでいる最も内側の for 文,while 文,または,do while 文を終了させ,それらの次の文に制御を移します.また,switch においても,しばしば使用されます.

(プログラム例 5.12 ) 繰り返しの中断( break ) 

  次のプログラムは,n 個のデータの和を求めるプログラムですが,負のデータが入力されると,その時点で for ループの外に出ます.

/**********************************/
/* データの和(負のデータで終了) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>

int main()
{
	int i1, n, x, sum = 0;
/*
	 データ数の入力
*/
	printf("データ数は? ");
	scanf("%d", &n);
/*
	 和の計算
*/
	for (i1 = 0; i1 < n; i1++) {
		printf("   データを入力して下さい ");
		scanf("%d", &x);
		if (x < 0)
			break;
		else
			sum += x;
	}
/*
	 出力
*/
	printf("和=%d\n", sum);

	return 0;
}
		

  多重ループから一度に抜け出したいような場合には,各ループ毎に break 文を使用する必要があります.以下に示す方法では,負のデータを入力すると,2 重ループの外側に一度に抜け出せます.もちろん,上のプログラムをこの方法で記述することも可能です.

/****************************/
/* 多重ループからの脱出     */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	int i1, i2, n, m, x = 0, sum = 0;
/*
	 データ数の入力
*/
	printf("クラス数は? ");
	scanf("%d", &n);
/*
	 和の計算
*/
	for (i1 = 0; i1 < n && x >= 0; i1++) {
		printf("人数は? ");
		scanf("%d", &m);
		for (i2 = 0; i2 < m && x >= 0; i2++) {
			printf("   データを入力して下さい ");
			scanf("%d", &x);
			if (x >= 0)
				sum += x;
		}
	}
/*
	 出力
*/
	printf("和=%d\n", sum);

	return 0;
}
		

5.2.2.2 continue 文

  continue 文は,for 文,while 文,または,do while 文本体の残りを実行せずに,次の繰り返しを実行します.break 文と似ていますが,continue 文では,break のように現在の繰り返し自身を終了させるわけではありません.

(プログラム例 5.13 ) 繰り返しの中断( continue ) 

  次のプログラムは,n 個のデータの和を求めるプログラムですが,負のデータが入力された場合はそのデータを除外します.

/**********************************/
/* データの和(負のデータを除外) */
/*      coded by Y.Suganuma       */
/**********************************/
#include <stdio.h>

int main()
{
	int i1, n, x, sum = 0;
/*
	 データ数の入力
*/
	printf("データ数は? ");
	scanf("%d", &n);
/*
	 和の計算
*/
	for (i1 = 0; i1 < n; i1++) {
		printf("   データを入力して下さい ");
		scanf("%d", &x);
		if (x < 0)
			continue;
		sum += x;
	}
/*
	 出力
*/
	printf("和=%d\n", sum);

	return 0;
}
		

演習問題5

[問1]年齢を読み込み,20 歳以上なら「大人」,そうでなければ「子供」と出力するプログラムを書け.

[問2]一つの整数データを読み込み,その値がゼロなら「0」,正なら「正」,それ以外なら「負」と出力するプログラムを,if 文を使用して書け.

[問3]試験の点数を読み込み,その点数が
60 点未満なら「不可」
60 点以上で 70 点未満なら「可」
70 点以上で 80 点未満なら「良」
80 点以上なら「優」
と出力するプログラムを書け

[問4]時間と分の変換を行うプログラムを書け.つまり,1 時間 20 分なら 80 分,また,90 分なら 1 時間 30 分に変換する.

[問5]閏年の判定を行うプログラムを書け.閏年は,年号が 4 で割り切れ,100 で割り切れない年である.ただし,400 で割り切れる年は含む.

[問6]1 度,2 度,・・・,及び,90 度に対し,各角度とその正弦の値(関数 sin )を出力するプログラムを書け(注:ヘッダファイル math.h も必要).

[問7]次の式の値を計算し,出力するプログラムを書け
(1)1.02+・・・+500.02
(2)1/1 + 1/2 + 1/3 + ・・・ + 1/20

[問8] n(入力)個のデータを読み込み,100 以上のデータ,及び,100 未満のデータの和をそれぞれ求めるプログラムを書け.

[問9]与えられた n 個のデータ内にある正の数と負の数の個数を出力するプログラムを書け.

[問10] n 個の抵抗(入力)を直列または並列に接続したときの合成抵抗を計算するプログラムを書け.例えば,n が 2 の場合,抵抗 R1 及び 抵抗 R2 を直列または並列結合したときの合成抵抗 R は,各々,以下のようになる

  

[問11] n の値を読み込んだ後,n! を計算し出力するプログラムを書け.

[問12] n,r の値を読み込んだ後,nr( = n! / (r! (n - r)!) )を計算するプログラムを書け.

[問13]正の整数値 n を読み込み,偶数の時はその値を 2 で割り,また,奇数の時はその値を 3 倍したものに 1 を加えるという処理を n の値が 1 になるまで繰り返すためのプログラムを書け.ただし,1 回の演算を行う毎に現在の n の値を表示するものとする.

[問14]n(入力)人の試験の点を入力した後,不可,可,良,及び,優の割合(%)を計算し,出力するプログラムを書け.ただし,
不可: 50 点未満
可 : 50 点から 60 点未満
良 : 60 点から 80 点未満
優 : 80 点以上
とする.

[問15] n 人に給与を支払うものとする.n 及び各人の給与の額をファイルから読み込み,すべての人に給与を釣り銭がないように支払うために必要な 10,000 円札,5,000 円札,1,000 円札,500 円硬貨,及び,100 円硬貨の合計枚数を出力するプログラムを書け.ただし,給与の最小単位は 100 円であるものとする.

[問16]九九の表を出力するプログラムを書け.

[問17]正の整数を入力し,それを各桁毎に分解し,それらの数字を正順及び逆順に,間に 1 つ以上の空白をあけて出力するプログラムを書け.例えば,12345 と入力したら,次のように出力することになる.ただし,整数の桁数は最大 9 桁であるとする.
1 2 3 4 5
5 4 3 2 1

[問18]整数型データの中で 1 になっているビットの数を出力するプログラムを書け.

[問19]複数個(未知)の実数データをファイルから読み込み,その最大値と最小値を出力するプログラムを書け.

[問20]ある学年に n(入力)クラスあり,各クラスには m(入力,クラス毎に異なる)人の生徒がいるものとする.全員に対しある試験を実施したとき,学年で最高点をとった生徒の点数及びその生徒が属するクラス番号を出力するプログラムを書け(最高点の生徒は一人だけであるとする).

[問21]ニュートン法により次式の根を求めるためのプログラムを書け.
f(x) = ex - 3x = 0
ただし,ニュートン法とは,関数 f(x) が単調連続で変曲点が無く,かつ,微分可能であるとき利用できる方法であり,根の適当な初期値 x0 から始めて,反復公式
xn+1 = xn - f(xn) / f'(xn)
を繰り返すことによって非線形方程式の解を求める方法である.この方法の幾何学的意味は右図に示すとおりである.収束判定の条件としては,
|xn+1 - xn| < ε
|f(xn+1) - f(xn)| < ε
|f(xn)| < ε
などがある.

[問22]二分法により次式の根を求めるためのプログラムを書け.
f(x) = ex - 3x = 0
ただし,二分法は,関数 f(x) が区間 [a, b] で連続で,かつ,根が1つだけ存在するとき利用できる方法である.従って,f(a)f(b) < 0 となる.区間 [a, b] の中点 c を
c = 0.5 * (a + b)
で求め,f(c) の値を計算し,f(a)f(c) < 0 なら
b = c,f(b) = f(c)
そうでなければ
a = c,f(a) = f(c)
と置き換え,根の存在区間を半分に縮小する.この操作を,
|b - a| < ε
が満足されるまで繰り返すことによって解を求める.

[問23]台形則により次の関数の任意区間の積分値を計算するプログラムを書け.
f(x) = (2π)-0.5-x*x/2
ただし,台形則は,積分区間[x1, x2]をn個の,幅
h = (x2 - x1) / n
の小区間に分け,各区間の面積を右図のような台形で近似して,積分値を計算する方法である.従って,積分値Sは次のように書ける.
S = 0.5h{f(x1) + 2(f(x1+h) + f(x1+2h) + ・・・ + f(x2-h)) + f(x2)}

静岡理工科大学 菅沼ホーム 全体目次 演習解答例 付録 索引