6.9 いくつかのプログラム
6.10.2 繰り返し文
Exercise 6.9.7 int, longのそれぞれの型で表現される最大の数に1を加えたらどうなるかを考察せよ.
Exercise 6.9.8 正の浮動小数点数の小数点以下を四捨五入した値を求めるプログラムを書け.
Exercise 6.9.9 AND, OR, NOTからXORを作れ.
Exercise 6.9.10 Example 6.8.7はどうしてかを考察せよ.
Exercise 6.9.11 intが16ビット,long が32ビットの時,-1L < 1U,-1L > 1ULとなる. 何故か?
6.10 文
Example 6.10.1 この例は, 0から9 までの整数を順に印字するものである.
#include <stdio.h>
int i ;
int main(int argc, char **argv) {
for(i=0;i<10;i++) { printf("%d\n",i) ; }
return 0 ; }
#include <stdio.h>
int i ;
int main(int argc, char **argv) {
for(i=0;i<10;i++) printf("%d\n",i) ; return 0 ;
}
この例では, はじめにi = 0が実行され, i < 10である間は, { }の部分が実行される. 各繰り返しが終 ると,i++が実行される. iが 9 になると, 繰り返しは実行されるが,それが終了した後, i++が実行され, iは10となる. したがって,i < 10が1 となり,繰り返しの文は実行されずに,forは終る.
Example 6.10.2 この例は,式1から式3が省かれたもので,無限ループを実現している.
#include <stdio.h>
int main(int argc, char **argv) {
for(;;) ; return 0 ; }
式2が省かれた場合は,常にその値がnon-zeroと認識される.
Example 6.10.3 この例は, 1から10までの和を計算している.
#include <stdio.h>
int i, j ;
int main(int argc, char **argv) {
for(i=0,j=0;i<10;i++) j += i+1 ;
return 0 ; }
ここで,コンマ演算子が2個所に利用されている. 特に,for文の第1式のコンマ演算子の利用法に注意.
Exercise 6.10.1 for文を利用して, 2 から10までの偶数の和を計算して印字するプログラムを書け.
Remark 6.10.1 1から10までの和を計算するプログラムでは,for文を使ったものでも, いろいろな書 き方が可能である.
j = 0 ;
for(i=0;i<10;i++) j += i+1 ;
上の例の書き方よりも,この方が望ましい. なぜなら,j=0と初期化を行う部分が独立し,プログラムの 意図が明確になっている.
j = 0 ;
for(i=1;i<=10;i++) j += i ;
確かに問題なく動作するのだが, Cでは境界判定条件(この例ではi <= 10)では, i <= 10と書くよ
りもi < 10と書く方が(すなわち, 直前の例の方が)標準的である. 可能な限り標準的なCの書き方を
身につける方が良い.
j = 0 ;
for(i=10;i>0;i--) j += i ;
普通に考えれば iの値をインクリメントするに決まっている. 間違いなく動くのだが, このような無理 な書き方はやめた方が良い. バグの元となる.
j = 0 ;
for(i=11;--i>0;j+=i) ;
もうここまでいくと,何をやっているのかわからない. 絶対ダメ!
6.10.2.2 while文
while 文は以下のような構文を持つ.
while(式) 文
ここで,式は省くことはできない. ここで,式は算術型もしくはポインタでなくてはならない.
このようなwhile 文は,次のように制御される.
1. 式が最初に評価される.
2. 式が0 でない限り,繰り返しの文が実行される.
ここで,式の副作用は,繰り返しのはじまる前に完了する.
Example 6.10.4 この例は, 0から9 までの整数を順に印字するものである.
#include <stdio.h>
int i=0;
int main(int argc, char **argv) {
while (i < 10) { printf("%d\n",i++) ; }
return 0 ; }
#include <stdio.h>
int i=0;
int main(int argc, char **argv) {
while (i < 10)
printf("%d\n",i++) ; return 0 ;
}
この例では, はじめにi = 0が実行され, i < 10である間は, { }の部分が実行される. 各繰り返し中で は,i++が実行されているので,繰り返し毎にiは1だけ増加する. iが9になると,繰り返しは実行され るが,繰り返しの文中で,i++が実行され,iは10となる. したがって,i < 10が1となり,繰り返しの文 は実行されずに,whileは終る.
Example 6.10.5 この例は,無限ループ25を実現している.
#include <stdio.h>
int i ;
int main(int argc, char **argv) {
while(1) ; return 0 ; }
Example 6.10.6 この例は, 1から10までの整数の和を計算するプログラムである.
#include <stdio.h>
int i=0,j=0;
int main(int argc, char **argv) {
while (i < 10) j += ++i ; return 0 ; }
ここでは和をとる直前に前置インクリメント演算子を利用していることに注意.
Exercise 6.10.2 while文を利用して, 2から10までの偶数の和を計算して印字するプログラムを書け.
25無限ループはウインドウシステムでのアプリケーションの作成に利用される.前に述べたforを使った無限ループか,こちらの 例かどちらかの利用にとどめておいた方が良い.無限ループの実現方法はいくらでも考え付くが,これら2つのどちらかであれば, C を少しでも知っているプログラマなら,見ただけで無限ループと理解可能である.
Remark 6.10.2 1から10までの和を計算するプログラムでは, while文を使ったものでも,いろいろな 書き方が可能である.
i = j = 0 ; while(i < 10) {
i += 1 ; j += i ; }
上の例の書き方よりも,この方が望ましいかもしれない. なぜなら,jに iの値をインクリメントする際 に, iがインクリメントされているというプログラムの意図が明確になっている.
j = 0 ; i = 1 ; while(i <= 10) {
j += i ; i += 1 ; }
確かに問題なく動作するのだが, Cでは境界判定条件(この例ではi <= 10)では, i <= 10と書くよ りもi < 10と書く方が(すなわち,直前の例の方が)標準的26である. 可能な限り標準的なCの書き方 を身につける方が良い.
j = 0 ; i = 10 ; while(i > 0) {
j += i ; i -= 1 ; }
普通に考えれば iの値をインクリメントするに決まっている. 間違いなく動くのだが, このような無理 な書き方はやめた方が良い. バグの元となる27.
結局, 1から10までの和をとるプログラムはfor文を用いた方が自然であることがわかる. じゃあ,while 文はどんな時に使うかって?それは,境界条件があらかじめ決まっていないときにはfor文よりもきれい になります.
Example 6.10.7 この例は,標準入力からEOF(ファイル終端)が検出されるまで,1文字づつをよみ,そ れを標準出力に書き出す.
26でも,この辺は難しいところかもしれない.
27でも,前のfor文を使ってデクリメントする例よりはよほどマシ.
#include <stdio.h>
int c ;
int main(int argc, char **argv) {
while((c = getchar())!= EOF) printf("%c",c) ;
return 0 ; }
このプログラムでは, c = getchar()により,標準入力から1文字を読み,読んだ文字がEOFでない間 は, その文字を標準出力に1文字づつ書き出している28.
このプログラムをgetchar.cとし,これを実行形式にコンパイルしたコマンドをa.out としたとき, ./a.out < getchar.c
を実行してみよ.
Remark 6.10.3 上の例で while(c = getchar() != EOF)
とすると,正しく動作しない. 代入演算子と等値演算子の優先順位は,等値演算子の方が上である.
6.10.2.3 do文
do文は以下のような構文を持つ.
do 文
while(式) ;
ここで,式は省くことはできない. ここで,式は算術型もしくはポインタでなくてはならない.
このようなdo文は,次のように制御される.
1. はじめに繰り返しの文が実行される.
2. それが終るたびに式が評価される.
3. 式が0 でない限り,繰り返しの文が実行される.
ここで,式の副作用は,各繰り返しの後に完了する.
Example 6.10.8 この例は, 0から9 までの整数を順に印字するものである.
28char cではなく,int cとしている理由は, Section 6.19を参照.
#include <stdio.h>
int i=0;
int main(int argc, char **argv) {
do {
printf("%d\n",i++) ; } while (i < 10) ; }
#include <stdio.h>
int i=0;
int main(int argc, char **argv) {
do
printf("%d\n",i++) ; while (i < 10) ; }
この例では,はじめにi = 0が実行される. 繰り返しの部分は,1回目は無条件に実行され,その後にi<10 が評価される. 各繰り返し中では,i++が実行されているので,繰り返し毎にiは 1だけ増加する. iが9 になると,繰り返しは実行されるが,繰り返しの文中で, i++が実行され, 繰り返しが終ると,iは 10とな る. したがって,i < 10が 1となり,繰り返しの文は実行されずに,doは終る.
doはwhileとは異なり,少なくとも1回は繰り返しの文が実行される.
Example 6.10.9 この例は, 1から10までの整数の和を計算するプログラムである.
#include <stdio.h>
int i=0,j=0;
int main(int argc, char **argv) {
do
j += ++i ; while(i < 10) ; }
ここでは和をとる直前に前置インクリメント演算子を利用していることに注意.
Exercise 6.10.3 do文を利用して, 2から10までの偶数の和を計算して印字するプログラムを書け.
やはり1 から10までの和を計算するプログラムを,do文を使っていろいろな書き方が可能である. しか し, これまでに見てきたように,わざわざプログラムを難しく書く必要はない.
do文はCでは余り多用される構造ではない. なぜなら, ループ終了条件が一番最後にあり,ひとめでは ループの構造がわかりにくいからである. 一般には,「必ず一度はループに入る」ということを明示的に示 したいときに用いるのだが, ループ全体が短く,ひとめでループの構造がわかるときだけに使うのが良い.
do文は,while 文とbreak 文の組合わせで同等なものを実現することが出来る.
6.10.2.4 繰り返し文と浮動小数点数
繰り返し文 for,while,do-while文を用いて,浮動小数点数の演算を行ってみよう. 例えば, 0.1の10 回の和を求めるというプログラムを考えてみよう.
Example 6.10.10 for文を用いて, 0.1 の和を計算する.
#include <stdio.h>
int main(int argc, char **argv) {
double x ;
for(x=0.0;x<0.5;x+=0.1) printf(‘‘%f\n’’, x) ; return 0 ;
}
#include <stdio.h>
int main(int argc, char **argv) {
double x ;
for(x=0.0;x<1.0;x+=0.1) printf(‘‘%f\n’’, x) ; return 0 ;
}
この2つのプログラムは,それぞれ, 0.1の4回,9回の和を計算するという意図を持つ. それぞれのプログ ラムは正しく実行できるだろうか?
Solaris 2.6 上のgcc 2.95.1を用いて実行すると,それぞれ
0.000000 0.100000 0.200000 0.300000 0.400000
0.000000 0.100000 0.200000 0.300000 0.400000 0.500000 0.600000 0.700000 0.800000 0.900000 1.000000
という出力を得る. 5回の和は期待通りに動作しているが,10回の和は余分な動作が含まれている.
Example 6.10.11 while文を用いて0.1の和を計算する.
#include <stdio.h>
int main(int argc, char **argv) {
double x=0.0 ; while(x != 0.5) {
x += 0.1 ;
printf(‘‘%f\n’’, x) ; }
return 0 ; }
#include <stdio.h>
int main(int argc, char **argv) {
double x=0.0 ; while(x != 1.0) {
x += 0.1 ;
printf(‘‘%f\n’’, x) ; }
return 0 ; }
同様にSolaris 2.6上のgcc 2.95.1を用いて計算すると,それぞれ
0.000000 0.100000 0.200000 0.300000 0.400000 0.500000
0.000000 0.100000 0.200000 0.300000 0.400000 0.500000 0.600000 0.700000 0.800000 0.900000 1.000000 1.100000 .
. . となり,右のプログラムは終了しない.
これら2つのプログラムは,浮動小数点数の誤差に起因し,正しく条件判断が行われていないことが原因と なる.
これらを正しく動作させるにはいくつかの方法がある.
Example 6.10.12 繰り返し回数を整数型変数を用いて制御する.
#include <stdio.h>
int main(int argc, char **argv) {
int i
double x=0.0;
for(i=0;i<10;i++) { x += 0.1 ;
printf("%f\n", x) ; }
return 0 ; }
#include <stdio.h>
int main(int argc, char **argv) {
int i=0 ; double x=0.0 ; while(i != 10) {
x += 0.1 ; i += 1 ; printf("%f\n", x) ; }
return 0 ; }
Example 6.10.13 浮動小数点数の誤差を見込んで制御する.
#include <stdio.h>
#include <math.h>
#define EP 1.0E-12
int main(int argc, char **argv) {
double x ;
for(x=0.0;fabs(x)<= 1.0+EP;x+=0.1) printf("%f\n", x) ;
return 0 ; }
#include <stdio.h>
#include <math.h>
#define EP 1.0E-12
int main(int argc, char **argv) {
double x=0.0 ;
while(fabs(x)< 1.0+EP) { printf("%f\n", x) ; x+=0.1 ;
}
return 0 ; }
このように,繰り返し文で浮動小数点数の計算を行うには,浮動小数点演算の誤差を考慮したプログラムを 書かなければならない.