for 文, while 文, do 文 は同じようなことを繰返しを実行する. このうち, for 文とwhile 文では, 繰返しを継続する条件の判定を, 繰返しの各実行の前に行う. これに対して, do 文では, 繰返しの各実行の後に行う. for 文と while文を比較すると, for文には初期化の機能と, 再度繰返しを実行する際の再初期化の機能が予め備わっている.
全てのプログラムにおいて, プログラムの構造が分かるようインデントを正しく行うこと.
授業終了時に, 作成したプログラム(.cが付くファイル)をメールで提出する. 本文に, 学籍番号, 氏名を明記すること.
繰り返し文であるfor文
for (式1; 式2; 式3) ループ本体は, 条件式である式2が真である間ループ本体を繰り返して実行する.
図1に示すように, for 文では, 式1で初期化した後, 制御式である式2が真ならばループ本体を実行する. その後式3により次のループ本体を実行するための再初期化を行う. 以下同様に, 制御式である式2を評価を行い, 式2が真であれば再度ループ本体を実行することを繰り返す. 式2を評価し, 偽であるときはループ本体の実行を行わないで, for 文を終了し, それ以降の文を実行する.
たとえば, 初めの項が3, 隣り合う項の差が2,
最後の項が13である等差数列の和
#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; }
for 文に関係する文法を整理しておく.
繰り返し文であるfor文は, ループ本体を制御式が真(非0)の間繰り返して実行する.
for (式1; 式2; 式3) ループ本体
// for () に続くブロックがループ本体として実行される. #include <stdio.h> #include <stdlib.h> int main(void) { int i; for (i = 1; i < 10; i++) { printf("i=%d", i); } // ブロック内部には複数の文を記述できる. for (i = 1; i < 10; i++) { printf(" i=%d", i); printf(" i*i=%d", i*i); printf("i*i*i=%d", i*i*i); } return EXIT_SUCCESS; }
/* ループ本体が1文のみからなるときは, ループ本体はブロックにしないで, その文のみを記述してもよい. */ #include <stdio.h> #include <stdlib.h> int main(void) { int i; for (i = 1; i < 10; i++) printf("i=%d", i); return EXIT_SUCCESS; }
/* 繰り返し文 for(式1; 式2; 式3) ループ本体 において, 式1, 式2, 式3 は無くてもよい. 式2の制御式を省略した場合, 非0の定数で置き換えられる. 以下の例は for ( i = 1; ; i = i + 1) の部分は for ( i = 1; 1 ; i = + 1) と同じ結果になる. 制御式が常に真であるので, 無限にループ本体の実行を繰り返す. */ #include <stdio.h> #include <stdlib.h> int main(void) { int i; for (i = 1; ; i = i + 1) { printf("%d ", i); } return EXIT_SUCCESS; }
/* 繰り返し文 for(式1; 式2; 式3) ループ本体 において, 式1, 式2, 式3 は無くてもよい. 式2の制御式を省略した場合, 非0の定数で置き換えられる. 以下の例は for ( ; ; ) の部分は for ( ; 1 ; ) と同じ結果になる. 従って, 無限にループ本体の実行を繰り返す. */ #include <stdio.h> #include <stdlib.h> int main(void) { for ( ; ; ) { printf("a "); } return EXIT_SUCCESS; }
gcc -std=c99 filename.cのようにC99のプログラムであることを示すオプション -std=c99を付ける.
/* C99では, 繰り返し文: for ( 節1; 制御式2; 式3 ) ループ本体 のうち節1の部分では宣言を実行することが可能である. 下の例では, 節1の部分で int i を宣言し, i=3 の初期化を行っている. gccでコンパイルするときは -std=c99 オプションが必要である: gcc -std=c99 filename.c */ #include <stdio.h> #include <stdlib.h> int main(void) { for (int i = 3; i < 10; i++) printf("i=%d\n", i); return EXIT_SUCCESS; }
/* 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 により現在のループ本体を終了する. 入力された正の整数の和を求めるプログラム. 負の整数が入力された場合とき, 現在のループ本体を終了する. 負の整数を不正な値とみなし, 和に加えない. */ #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; }
#include <stdio.h> #include <stdlib.h> int main(void) { int a, offset, x; a = 50; for (offset = 0; offset <= 10; offset = offset + 1) { x = a + offset; printf("x = %d\n", x); } return EXIT_SUCCESS; }
#include <stdio.h> #include <stdlib.h> int main(void) { int i; for (i = 0; i < 10; i = i + 1) { puts("hello"); } return EXIT_SUCCESS; }
i<=9と表現すると, ループ本体を10回実行するはずなのに, それと異なる9を用いて条件を記述すると 可読性が低い. また, 再初期化を行う際に i = i + 2 のように1以外の増減をするとき, 10 という数値を用いて条件式を記述していないと, 誤りが起きやすい.
i!=10 は上記の問題を含まないが, iがいずれは必ず10になるよう十分注意を払わないと, 無限回ループ本体を実行することになってしまう. とくに, 再初期化を行う際に i = i + 3 のように1以外の増減をするとき, 誤りが起きやすい.
/* このプログラムでは, iに代入される値は 0, 3, 6, 9, 12, ... であり, 10 になることはないので, 無限回ループ本体を実行してしまう. */ #include <stdio.h> #include <stdlib.h> int main(void) { int i; for (i = 0; i != 10; i = i + 3) { printf("i=%d\n", i); } return EXIT_SUCCESS; }
結局, 制御式には i < 10 の表現が多用される.
例えば, 複合代入演算子を用い i = i + 3 は i += 3 と表現できる.
#include <stdio.h> #include <stdlib.h> int main(void) { int i; for (i = 0; i < 10; i += 3) { printf("i=%d\n", i); } return EXIT_SUCCESS; }
さらに, 1つだけ増減する場合は, 後置増分演算子などを用いる. i = i + 1 は i++ と表現できる.
#include <stdio.h> #include <stdlib.h> int main(void) { int i; for (i = 0; i < 10; i++) { printf("i=%d\n", i); } return EXIT_SUCCESS; }
#include <stdio.h> #include <stdlib.h> int main(void) { double x, start, end; start = 0.0; end = 5.0; for (x = start; x <= end; x += 1.0) { printf("x=%f\n", x); } return EXIT_SUCCESS; }
制御式は, 等号を用いないで記述する.
#include <stdio.h> #include <stdlib.h> int main(void) { double x, start, end; start = 0.0; end = 5.0; for (x = start; x < end + 0.1; x += 1.0) { printf("x=%f\n", x); } return EXIT_SUCCESS; }
#include <stdio.h> #include <stdlib.h> int main(void) { int a, offset, x; for (a = 50, offset = 0; offset <= 10; offset = offset + 1) { x = a + offset; printf("x = %d\n", x); } return EXIT_SUCCESS; }
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はであるので, その結果である 500 が a に代入されている.
#include <stdio.h> int main(void) { int a; a = 10; a = a * 50; printf("b=%d\n", a); return 0; }
変数は, 初めにある定まった値を代入し, その後を変更して用いる. 次の例では初めに 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 が代入されている必要がある. この代入を行わなかった場合, 初期値は偶然決まった値となり, 動作は定まっていない. 宣言を行った後に, 別の箇所で初期化することにすると, 忘れてしまうことがあるので, 宣言時に初期化するのが望ましい. また, 初期化が必要ない変数であっても, プログラムを作り変えるうちに初期化が必要となってしまうこともある. 従って, 初期化の必要がない変数でも, ミスを防ぐ上で明示的に初期化を行っておいた方がよい.
(教科書 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; |
整数 |
*1) 厳密には文法上算術演算に分類されています.
(教科書 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; }
// 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文 用いる.
C 言語では, 文の終わりを明示する記号(;)を導入することで, 語と語の間では, 自由な位置で改行したり, タブやスペースを入れたりすることができる.
これにより, プログラムの構造を視覚的に捉え易くすることができる. 基本的には, 字下げ(インデント)を用い, ブロックの範囲が分かるようにする. ブロックの初めと終わりは, 同じ深さとし, ブロック内部は1段階下げる(右に深くする). 波括弧や空白の入れ方などのそれ以外の部分は, 色々な流儀があるが, 歴史的な経緯から K&R の形式が基本となっている.
例
// y = x^n (xのn乗) を求める. #include <stdio.h> #include <stdlib.h> int main(void) { int i, n; double x, y; y = 1.0; x = 3.0; n = 3; for(i=0; i<n; i++) y *= x; printf("%gの%d乗は%gです.\n", x, n, y); return EXIT_SUCCESS; }
// Taylor 展開により sin(x) を x=0 の近傍で近似する. #include <stdio.h> int main(void) { double x, y; x = 0.2; y = x - 1.0/(3*2*1) * x*x*x + 1.0/(5*4*3*2*1) * x*x*x*x*x - 1.0/(7*6*5*4*3*2*1) * x*x*x*x*x*x*x; printf("sin(%g) = %g\n", x, y); return 0; }
(c)1999-2013 Tetsuya Makimura