Subsections

4章 プログラムの流れの繰り返し do, while, for

for 文, while 文, do 文 は同じようなことを繰返しを実行する. このうち, for 文とwhile 文では, 繰返しを継続する条件の判定を, 繰返しの各実行の前に行う. これに対して, do 文では, 繰返しの各実行の後に行う. for 文と while文を比較すると, for文には初期化の機能と, 再度繰返しを実行する際の再初期化の機能が予め備わっている.

4.1 本日の課題

全てのプログラムにおいて, プログラムの構造が分かるようインデントを正しく行うこと.

授業終了時に, 作成したプログラム(.cが付くファイル)をメールで提出する. 本文に, 学籍番号, 氏名を明記すること.

4.2 for 文

繰り返し文であるfor文

    for (式1; 式2; 式3) ループ本体
は, 条件式である式2が真である間ループ本体を繰り返して実行する.

Figure 1: for文の構文
\includegraphics{figs/for.eps}

1に示すように, for 文では, 式1で初期化した後, 制御式である式2が真ならばループ本体を実行する. その後式3により次のループ本体を実行するための再初期化を行う. 以下同様に, 制御式である式2を評価を行い, 式2が真であれば再度ループ本体を実行することを繰り返す. 式2を評価し, 偽であるときはループ本体の実行を行わないで, for 文を終了し, それ以降の文を実行する.

たとえば, 初めの項が3, 隣り合う項の差が2, 最後の項が13である等差数列の和

\begin{displaymath}
3 + 5 + 7 + 9 + \cdots + 13
\end{displaymath} (1)

を求めプログラムは次のようになる.
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i, sum;
	sum = 0;

	for ( i = 3; i <= 13; i = i + 2 ) {
		sum = sum + i;
	}

	printf("3+5+7+...+13=%d\n", sum);
	return EXIT_SUCCESS;
}

  1. まず, 式1の部分で, i=3によりiを3に初期化する.
  2. 次に, 式2の部分で, ループ本体を実行するかループを終了するかを判定する. この時点では, iは3であるので i<=13は真となるので, ループ本体を実行することになる.
  3. ループ本体では, sum = sum + i; が実行される. この時点では, iは3, sumは0 であるので, sum に3が代入される.
  4. ループ本体の実行が終わると, 式3の部分 i=i+2 が実行される. この時点では, iは3であるので, iに3+2 の結果が代入されることになる.
  5. 再度式2の評価が行われ, ループ本体を実行するか, ループを終了するかが 判別される. このときには, 初めに式1を評価したときと比較し, それとは異なる式3が実行されている. また, ループ本体で変数の値が変更されている場合もある. このような新しい条件の下で, 再度式2が評価されることになる. この時点では, iは5であるので, i<=13は真になり, 再度ループ本体を実行することになる.

  6. ...

  7. その後, ループ本体の実行が終了したのち式3を実行し, 式2が真である場合にはループ本体を実行を繰り返す.
  8. ...
  9. iが13でループ本体を終了したとき, 式3で iに15が代入される. その後式2が評価されるとi<=13は偽になる. このときは, ループ本体を実行すること無く, ループを終了する.

4.2.1 for文の構文規則

for 文に関係する文法を整理しておく.

繰り返し文であるfor文は, ループ本体を制御式が真(非0)の間繰り返して実行する.

for (式1; 式2; 式3) ループ本体

4.2.2 break, continue: ループ本体内での制御

Figure 2: for 文における continue, break.
\includegraphics{figs/for_continue_break.eps}

break
for 文のループ本体に break があると, その文で for 文を 終了する.
/*
 break により現在のfor文を終了する.

 入力された正の整数の和を求めるプログラム.
 負の整数が入力された場合ときにfor文を終了する.

 a) 負の整数が入力されたとき, 入力終了であることにしておけば,
    任意の数の正の整数の和を求めることができる.

 b) また, break 文により, 不正な値が入力されたとき入力を終了こともできる.
*/

#include <stdio.h>

int main(void)
{
    int n, x, sum = 0;

    for ( n = 0; n < 10; n++ ) {
	scanf("%d", &x);
	if ( x < 0 ) {
	    printf("x is negative.\n");
	    break;
	}
	sum = sum + x;
    }

    printf("sum = %d\n", sum);

    return 0;
}

continue
for 文のループ本体に break があると, その文で for の現 在のループを終了する.
/*
 continue により現在のループ本体を終了する.

 入力された正の整数の和を求めるプログラム.
 負の整数が入力された場合とき, 現在のループ本体を終了する.
 負の整数を不正な値とみなし, 和に加えない.
*/

#include <stdio.h>

int main(void)
{
    int n, x, sum = 0;

    for ( n = 0; n < 10; n++ ) {
	scanf("%d", &x);
	if ( x < 0 ) {
	    printf("x is negative.\n");
	    continue;
	}
	sum = sum + x;
    }

    printf("sum = %d\n", sum);

    return 0;
}

より一般には, continue は 繰り返し文であるfor文, while文, do文で使用され次の機能を有する.

continue文は, それを囲む最も内側の繰返し文のループ継続部, すなわちループ本体の終わりへの分岐を引き起こす. [X3101:2003(ISO/IEC 9899:1999) 6.8.6.2]

また, break は switch文および繰り返し文(for, while, do) で使用され次の機能を有する.

break文は, それを囲む最も内側のswitch文又は繰り返し文の実行を終了させる. [X3101:2003(ISO/IEC 9899:1999) 6.8.6.3]

例 while 文のループ本体中での break と continue

// 正の数を入力し, 和を求めるプログラム.
// 負の数が入力された場合は, 和に加えない.
// 0 が入力されたら, 入力終了とする.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int a = 0, sum = 0;
	
	while (1) {
		scanf("%d", &a);

		if (a < 0) {
			printf("不正な入力です.\n");
			continue;
		}

		if (a == 0) {
			printf("入力終了\n");
			break;
		}

		sum += a;
	}
	
	printf("入力された正の整数の和は %d です.\n", sum);
	
	return EXIT_SUCCESS;
}

4.2.3 for文の実際

4.3 変数の演算

4.3.1 代入式

教科書p.23

C言語における代入式では, 数学と異なり, 右辺の結果を左辺に代入する. 次の例では, a * 50 の結果を bに代入している.

#include <stdio.h>

int main(void)
{
    int a, b;

    a = 10;
    b = a * 50;

    printf("b=%d\n", b);
    return 0;
}

また, 次のように右辺に左辺と同じ変数が現れることも多い. この場合も数学とは異なり, 右辺の結果を左辺に代入していることになる. a * 50はであるので, その結果である 500a に代入されている.

#include <stdio.h>

int main(void)
{
    int a;

    a = 10;
    a = a * 50;

    printf("b=%d\n", a);
    return 0;
}

4.3.2 初期化子

教科書 p.64

変数は, 初めにある定まった値を代入し, その後を変更して用いる. 次の例では初めに sum を 0.0に初期化し, その後変数sumを用いている.

// sum = 1/1 + 1/2 + 1/3 + 1/4 + 1/5 を求める.

#include <stdio.h>

int main(void)
{
    double sum;

    sum = 0.0;
    for(x=0.0; x<5.1; x+=1.0)
	sum += 1.0/x;

    return 0;
}

このような初めに代入されているべき値は, 変数を宣言したときに代入し初期化することもできる.

//  sum = 1/1 + 1/2 + 1/3 + 1/4 + 1/5 を求める.

#include <stdio.h>

int main(void)
{
    double sum = 0.0;

    for(x=0.0; x<5.1; x+=1.0)
	sum += 1.0/x;

    return 0;
}

変数宣言したときでもよいし, 宣言をした後でもよいが, この例ではsumは初めに0.0 が代入されている必要がある. この代入を行わなかった場合, 初期値は偶然決まった値となり, 動作は定まっていない. 宣言を行った後に, 別の箇所で初期化することにすると, 忘れてしまうことがあるので, 宣言時に初期化するのが望ましい. また, 初期化が必要ない変数であっても, プログラムを作り変えるうちに初期化が必要となってしまうこともある. 従って, 初期化の必要がない変数でも, ミスを防ぐ上で明示的に初期化を行っておいた方がよい.

4.3.3 複合代入演算子

(教科書 Table 4-2) y, x は整数または実数. a, b は整数.
種類 演算子 用例 意味 対象
算術演算 += y += x; y = y + x; 整数, 実数
算術演算 -= y -= x; y = y - x; 整数, 実数
算術演算 *= y *= x; y = y * x; 整数, 実数
算術演算 /= y /= x; y = y / x; 整数, 実数
算術演算 %= b %= a; b = b % a; 整数
ビット演算 <<= b <<= a; b = b << a; 整数
ビット演算 >>= b >>= a; b = b >> a; 整数
ビット演算 &= b &= a; b = b & a; 整数
ビット演算*1 ^= b ^= a; b = b ^ a; 整数
ビット演算 |= b |= a; b = b | a; 整数
ビット演算については第7章で学びます.

*1) 厳密には文法上算術演算に分類されています.

4.3.4 後置演算子, 前置演算子

(教科書 Table 4-3)
種類 用例 意味 対象
後置演算子 a++ aの値を参照したのち a = a + 1; を実行. 整数
後置演算子 a-- aの値を参照したのち a = a - 1; を実行. 整数
前置演算子 ++a a = a + 1; を実行したのち aの値を参照. 整数
前置演算子 --a a = a - 1; を実行したのち aの値を参照. 整数

以下のプログラムにより後置演算子と前置演算子の相違を理解しましょう.

// a++ と ++a の相違.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int a;

	// a++; により a = a + 1; が実行される.
	a = 3;
	printf("[    1] a=%d\n", a);
	a++;
	printf("[    2] a=%d\n", a);

	// aの値を参照したのちに a = a + 1; が実行される.
	// これにより プログラムを簡潔に記述できる.
	a = 3;
	printf("[a++ 1] a=%d\n", a);
	printf("[a++ 2] a=%d\n", a++);
	printf("[a++ 3] a=%d\n", a);

	// a = a + 1; を実行したのち aの値を参照される.
	a = 3;
	printf("[++a 1] a=%d\n", a);
	printf("[++a 2] a=%d\n", ++a);
	printf("[++a 3] a=%d\n", a);

	
	return EXIT_SUCCESS;
}
// a-- と --a の相違.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int a;

	// a--; により a = a - 1; が実行される.
	a = 3;
	printf("[    1] a=%d\n", a);
	a--;
	printf("[    2] a=%d\n", a);

	// aの値を参照したのちに a = a - 1; が実行される.
	// これにより プログラムを簡潔に記述できる.
	a = 3;
	printf("[a-- 1] a=%d\n", a);
	printf("[a-- 2] a=%d\n", a--);
	printf("[a-- 3] a=%d\n", a);

	// a = a + 1; を実行したのち a の値を参照される.
	a = 3;
	printf("[--a 1] a=%d\n", a);
	printf("[--a 2] a=%d\n", --a);
	printf("[--a 3] a=%d\n", a);
	
	return EXIT_SUCCESS;
}

4.4 多重ループ

4.5 for文, while文, do文相互の書き換え

以下のプログラムはすべてまったく同等の有する. このように, for文, while文, do文は相互に書き換えることが可能である.

// 0から9までの和を求める

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i, sum;

	for (i = 0, sum = 0; i < 10; i++) {
		sum += i;
	}

	printf("sum=%d\n", sum);
	return EXIT_SUCCESS;
}
// 0から9までの和を求める

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i, sum;

	// 初期化
	i = 0;
	sum = 0;

	while (1) {
		if ( ! (i < 10) ) // 制御式
			break;

		sum += i;

		// 再初期化
		i++;
	}
	
	printf("sum=%d\n", sum);
	return EXIT_SUCCESS;
}
// 0から9までの和を求める

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i, sum;

	// 初期化
	i = 0;
	sum = 0;

	for ( ; ; ) {
		if ( ! (i < 10) ) // 制御式
			break;

		sum += i;

		// 再初期化
		i++;
	}
	
	printf("sum=%d\n", sum);
	return EXIT_SUCCESS;
}
// 0から9までの和を求める

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i, sum;

	// 初期化
	i = 0;
	sum = 0;

	do {
		if (i < 10) { // 制御式
			sum += i;
		} else {
			break;
		}

		// 再初期化
		i++;
	} while (1);
	
	printf("sum=%d\n", sum);
	return EXIT_SUCCESS;
}
// 0から9までの和を求める

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i, sum;

	// 初期化
	i = 0;
	sum = 0;

	do {
		if ( ! (i < 10) ) // 制御式
			break;

		sum += i;

		// 再初期化
		i++;
	} while (1);
	
	printf("sum=%d\n", sum);
	return EXIT_SUCCESS;
}

for文, while文, do文はすべて制御式とループ本体を持つ点が共通する. 制御式がループ本体より前で必要な場合は, for文 または while文 を用いる. そのうち, 初期化や再初期化が必要な場合は for文を用い, 必要でないときは while 文を用いる. また, 制御式がループ本体より後で必要な場合は, do文 用いる.

4.6 while

Figure 3: while文の構文
\includegraphics{figs/while.eps}

4.7 do...while

Figure 4: do文の構文
\includegraphics{figs/do.eps}

4.8 自由形式, インデント

C 言語では, 文の終わりを明示する記号(;)を導入することで, 語と語の間では, 自由な位置で改行したり, タブやスペースを入れたりすることができる.

これにより, プログラムの構造を視覚的に捉え易くすることができる. 基本的には, 字下げ(インデント)を用い, ブロックの範囲が分かるようにする. ブロックの初めと終わりは, 同じ深さとし, ブロック内部は1段階下げる(右に深くする). 波括弧や空白の入れ方などのそれ以外の部分は, 色々な流儀があるが, 歴史的な経緯から K&R の形式が基本となっている.


4.9 for, while, do を用いたプログラム

(c)1999-2013 Tetsuya Makimura