ここまで、プログラムの実行順序を制御する構文として、「指定回数だけ繰り返す」ための for{;;}
(C)または do-enddo
(Fortran)および「条件判断」のための if{} else if{} else{}
(C)または if-else if-else-endif
(Fortran)を扱った。
これだけを知っていればかなり多くの場合を扱えるのだけど、場合によってはかなり煩雑なプログラムを書くはめになるかもしれない。
いくらサバイバル・プログラミングとは言ってももうひとつだけ実行制御を知っておいたほうが便利だろう。それは
「条件が満たされている間繰り返し」これを今まで取り上げなかったのには理由がある。いわゆる
「永久ループ」が容易に作れてしまうのだ。
for
や do
なら、なにがあろうと指定回数だけ繰り返せば終わってくれるので「永久ループ」にはならないのだが、今回扱うループ構造は「条件」を間違えるとたちまち永久ループになるので、要注意。
Cでは while
という命令を使う。
|
i<10
が満たされている間、iを1ずつ増やし、その値を出力する。
もっとも、これだけなら
|
while
はまず最初に条件が満たされているかどうかを調べ、満たされていれば{}内を実行する、という順序で実行されるので不等号の範囲に注意する。
上の例はループの実行回数が自明だったので for
でも書くことができた(というより、普通は for
で書く)。while
は、ループが何回実行されるかわからない場合に威力を発揮する。
たとえば
|
while
を使うと、ファイルからデータを次々と読み込ませるプログラムでデータ数をあらかじめ指定しないことが可能になる(もっとも、データ数を知らずに計算するのはむしろ問題なので、あえてデータ数指定のプログラムを書くほうがいいかもしれない)。
|
EOFはファイルの終わりを表わす特殊記号である(End of file)。
今まで秘密にしていたが、scanf
は整数型の関数で、
それ自体がある整数値を返す。実はそれはちゃんと読めたデータの個数
(上の例では一度に一個しかデータを読まないので、1)なのだが、
ファイルの終わりを検知すると個数ではなくEOFを返す。
つまり、while
の中の条件 scanf("%d",&x) != EOF
は
「標準入力から整数変数xに値をひとつ読み、それがファイルの終わりでなければ」
というもの。
なお、端末から直接データを入力する場合は、EOF記号はCtrl-D(Ctrlキーを
押しながらDを押す)によって発生させられるので、最後のデータを入力したら次に
Ctrl-Dを押せばよい(ただし、これはUNIX系OSの場合)。
Fortranでも do-enddo
と if-exit
という構文を組み合わせて、while
に相当する構文を書くことができる。
ただし、これはFortran90と呼ばれる言語の仕様であり、
Fortran77と呼ばれる古い仕様には含まれていない。
一方、LINUXなどでよく使われるFortranコンパイラであるg77はその名のとおりFortran77
に基づいているのだが、一部Fortran90の仕様がとりいれられている。
実はこれまでに使ってきた do-enddo
もそうなのだった。if-exit
もg77で使えることを確認してある。
|
while
構文とは逆に「条件が満たされなければループを抜ける」という書き方になっていることに注意。推測できると思うが、if-exit
は do-enddo
ループの中のどこにでも置くことができるので、それによって条件判断をいつ行なうかを好きに決められる。たとえば、
|
i=10
としてみれば、ふたつの違いが明らかになるだろう。
ループが何回実行されるかわからない場合の例として、 整数xを2で何回割ることができるかを計算するプログラムのFortran版を挙げる。
|
if-exit
の置かれている位置に注意すること。
また、mod(x,2)
はxを2で割った余りを与える関数である。
なお、x/2
は整数同士の割り算なのでxが偶数でなければ余りは切り捨てられてしまうことに注意。
Fortranでも読み込むデータ数をあらかじめ指定することなく、 ファイルからデータを次々と読み込ませるプログラムを書くことはできる。
|
io(名前はなんでもいいが)を整数変数と宣言し、
read
文のかっこ内に書かれた
IOSTAT=io
という部分が、データの読み込み状況を
変数ioに格納するという指定である。
IOSTATはデータが正常に読まれれば0、ファイルの終わりを検知すると負の値を
出力するので、それが変数ioに格納される。if
文でioの値が負か
どうかを調べ、負ならループを抜ければよい。
なお、端末から直接データを入力する場合は、EOF(ファイルの終わり)記号はCtrl-D(Ctrlキーを
押しながらDを押す)によって発生させられるので、最後のデータを入力したら次に
Ctrl-Dを押せばよい(ただし、これはUNIX系OSの場合)。
/* 指数関数の値をテーラー展開によって求める。
関数の引数は端末から入力する。
収束判定をwhileによって行なう。
収束精度はPRECという値で指定する。
ただし、このプログラムは小さな値を足した場合の「積み残し誤差」
について、まったく考慮していないので、このままでどんな級数に
も応用できる限らない
*/
#include<stdio.h>
#include<math.h> 検算のための指数関数と絶対値関数を使うので必要
#define PREC 0.0000001 収束の精度をPRECという名前で定義しておく
main()
{
double x_old,x_new,z,f;
int i;
scanf("%lf",&z); 値を端末から入力
x_old = 0; 級数の初期値 (0項目)
x_new = 1; 級数の1項目は1 (次数は0)
i = 0; 次数の初期値(0)
f = 1; fには階乗を計算した値がはいる(整数型にしないのは、値が巨大になっても対応できるように)
while( fabs((x_old-x_new)/x_old) > PREC){ x_oldとx_newの差の相対値がPRECになるまでループを回す
x_old = x_new; 現在の値をx_oldにいれる
++i; 次数をひとつ上げる
f *= i; 次の階乗を計算(fが階乗になることを理解しよう)
x_new += pow(z,i)/f; 現在の次数の項を足す(powはzのi乗を与える)
printf("%d %15.12f %15.12f\n",i,x_new,exp(z)); 次数と現在の値とexpの本当の値を出力
} ループはここまで
} プログラム終了
/*
個数不明のデータを読んで、データの個数とそれらの平均
を計算するプログラム (program 4の書き換え)
データを端末から入力する場合は最後のデータの次にCtrl-D
*/
#include<stdio.h>
main()
{
double y,sum;
int n;
n = 0;
sum = 0;
while(scanf("%lf",&y) != EOF){ データは倍精度実数とする
++n
sum += y;
}
printf("number of data= %d\n",n); データ数を出力
printf("average= %f\n",sum/n); 平均値を出力
}
program 22
/*
ふたつの整数の最小公倍数を求めるユークリッド互除法
整数をn>mとすれば、nをmで割る。余りをqとする。
q = 0 なら、mが答
そうでなければ、n=m, m=qとして余りが出なくなるまで繰り返す
余りが0になったとき、最後に割った数が答
なぜ、それでいいのか考えてみよう
*/
#include<stdio.h>
#include
main()
{
int x1,x2;
int n,m,q;
scanf("%d",&x1); 整数データをふたつ読む
scanf("%d",&x2);
if(x1 >= x2) {n = x1; m = x2;} 大きいほうをn、小さいほうをmとする
else {n = x2; m = x1;}
while( (q = n % m) > 0) { nをmで割った余りが正の間繰り返し(条件判定の前に計算をしている。こういう書き方も可能)
n = m; 古いmを新しいnに
m = q; 余りqを新しいmに
}
printf("%d\n",m); q=0となったときのmが答
}
FORTRAN版
program 20
c 指数関数の値をテーラー展開によって求める。
c 関数の引数は端末から入力する。
c 収束判定をwhileによって行なう。
c 収束精度はPRECという値で指定する。
c ただし、このプログラムは小さな値を足した場合の「積み残し誤差」
c について、まったく考慮していないので、このままでどんな級数に
c も応用できる限らない
program main
parameter ( prec = 0.0000001) 収束条件をprecという名前の定数にする
real*8 x_old, x_new, z, f
integer i,j,k
read(*,*)z 値を端末から入力
x_old = 0 級数の初期値 (0項目)
x_new = 1 級数の1項目は1 (次数は0)
i = 0 次数の初期値(0)
f = 1 fには階乗を計算した値がはいる(整数型にしないのは、値が巨大になっても対応できるように)
do ループはここから
if( abs((x_old-x_new)/x_old) .le. prec)exit x_oldとx_newの差の相対値がprec以下ならループを抜ける
x_old = x_new 現在の値をx_oldにいれる
i = i + 1 次数をひとつ上げる
f = f * i 次の階乗を計算(fが階乗になることを理解しよう)
x_new = x_new + z**i/f 現在の次数の項を足す
write(*,*)i,x_new,exp(z) 次数と現在の値とexpの本当の値を出力
enddo ループはここまで
end プログラム終了
program 21
C 個数不明のデータを読んで、データの個数とそれらの平均
C を計算するプログラム (program 4の書き換え)
C データを端末から入力する場合は最後のデータの次にCtrl-D
program main
real*8 y,sum 読むデータは倍精度とする
integer i,io
n = 0
sum = 0
do
read(*,*,IOSTAT=io)y
if (io .lt. 0) exit 終了判定
n = n+1
sum = sum+y
enddo
write(*,*)"number of data= ",n データ数を出力
write(*,*)"average= ",sum/n 平均を出力
end
program 22
C ふたつの整数の最小公倍数を求めるユークリッド互除法
C 整数をn>mとすれば、nをmで割る。余りをqとする。
C q = 0 なら、mが答
C そうでなければ、n=m, m=qとして余りが出なくなるまで繰り返す
C 余りが0になったとき、最後に割った数が答
C なぜ、それでいいのか考えてみよう
program main
integer x1,x2
integer n,m,q
read(*,*)x1 整数データをふたつ読む
read(*,*)x2
if (x1 .ge. x2) then 大きいほうをn、小さいほうをmとする
n = x1
m = x2
else
n = x2
m = x1
endif
do ここからループ
q = mod(n,m) qはnをmで割った余り
if (q .eq. 0) exit qが0ならループを抜ける
n = m 古いmを新しいnに
m = q 余りqを新しいmに
enddo
write(*,*)m q=0となったときのmが答
end