Previous Top Next

第 03 回 ループ

多くのプログラムでは何らかの類似の処理を繰り返し行う。 これをループという。 ループのためには for 文while 文の 二つが用意されている。

ここではこれらの文の基本的な使い方を学ぶ。


ループ - for 文


次のプログラムは 1 から 10 までの数を順番に表示するものである。

/* for */
#include <stdio.h>

int main(int argc, char *argv[])
{
  int i;

  for (i = 1; i <= 10; i++)
    printf("%d\n", i);

  return 0;
}
for の後のカッコ内は以下のような意味をもつ。

for ( 初期化 ; 継続条件 ; インクリメント)

このプログラムでは、まず i = 1 で i に値 1 が代入される。 数学では a = b と b = a は同じ意味をもつが、C 言語では違う。 記号 = は常に右辺の値を左辺に代入するという意味をもつ。 代入された i = 1 の状態で継続条件がチェックされる。 継続条件が満たされないときにはループは終了する。 継続条件が満たされた場合に、カッコ以下の命令が実行される。 この例では i = 1 は i <= 10 を満たしているので printf が実行される。 それが終わると再び for の位置に戻ってくる。 このときインクリメントで指定した命令が実行される。 i++ は i = i + 1 と同じ意味をもつインクリメント演算子である。 i = i + 1 というのはおかしな気がするが、先に述べたように = は代入を意味するので この代入は許され、結果として i の値が 1 だけ増える。 同じインクリメント演算子に ++i がある。 i++ の場合には i が評価された後に i = i + 1 となるが ++i は i が評価される前に i = i + 1 となる。 またデクリメント演算子 i--, --i も同様である。 同様の命令に i += 2 などがある。 この命令によって i = i + 2 が実行される。 i -= 2, i *= 2 なども同様である。 上のプログラムでは命令が繰り返され i = 11 となったときに継続条件が偽となりループは終了する。

課題 03.01
次のプログラムを実行し、インクリメント演算子の意味を考えよ。

#include <stdio.h>

int main(int argc, char *argv[])
{
  int i;

  i = 1;
  printf("%d\n", i++);
  printf("%d\n", i);

  i = 1;
  printf("%d\n", ++i);
  printf("%d\n", i);

  i = 1;
  printf("%d\n", i);
  i += 2;
  printf("%d\n", i);

  return 0;
}

与えられた自然数が素数であるかどうかを判定するプログラムを書いてみる。 いくつかの新しいことを用いる。

 1  #include <stdio.h>
 2  #include <math.h>
 3
 4  int main(int argc, char *argv[])
 5  {
 6    int i, n, s;
 7 
 8    printf("Input a positive integer : ");
 9    scanf("%d", &n);
10
11    if (n == 2) {
12      printf("%d is a prime number\n", n);
13      return 0;
14    }
15  
16    if (n % 2 == 0) {
17      printf("%d is not a prime number\n", n);
18      return 0;
19    }
20
21    s = sqrt(n);
22    for (i = 3; i <= s; i += 2) {
23      if (n % i == 0) {
24        printf("%d is not a prime number\n", n);
25        return 0;
26      }
27    }
28
29    printf("%d is a prime number\n", n);
30  
31    return 0;
32  }
まず 8, 9 行目で自然数 n を入力する。 11 行目から 19 行目で、自明な場合として n = 2 の場合と n が偶数の場合を処理する。 ここで n % 2 は n を 2 で割った余りを意味する。 したがって n % 2 == 0 というのは n が偶数であるかどうかということである。 これらの場合には後の計算は不要なので return 0 でプログラムを終了させる。 これまでのプログラムでは最後に return 0 と書いてあったが、プログラムのどの場所でも return を書いてプログラムを終了させることができる。 しかし大規模なプログラムで return が多くの場所に書いてあると、 その管理が大変になるのであまり良いことではない。 次にループに入るが、自然数 n が素数でないとすると n = a * b となる 1 より大きい自然数 a, b が存在し、 その一方は n の平方根以下となる。 したがって n が素数かどうかを判定するには n の平方根以下の数で割り切れないことを見れば良い。 そのために 22 行目で平方根を求める組み込み関数 sqrt を用いている。 sqrt をはじめとする多くの数学関数を用いるためには math.h を読み込まなくてはならず、 そのために 2 行目がある。 また sqrt を利用するには libm というライブラリを利用することになり、そのためにコンパイル時に
home$ gcc prime.c -lm
などとする必要がある。 また予め偶数を除いてあるので、奇数のみで割ってみればよく i += 2 としている。 n % i == 0 となる i があれば、素数ではないことが分かるので、 すぐにメッセージを表示してプログラムを終了する。 すべてのチェックをクリアした場合だけプログラムの最後に到達し、 素数であることが表示される。

このプログラムにはもう一つ説明の必要がある部分がある。 組み込み関数 sqrt は一般に実数値を返す。 しかしこのプログラムでは、その値を整数型である変数 s に代入している。 この様な場合、C 言語では型の変換(キャスト)が行われる。 具体的には、実数の少数部分が切り捨てられて整数になる。 逆に整数値を実数型変数に代入することもできる。 型のキャストを明示的に書きたいときには

s = (int)sqrt(n);
のようにキャスト先の型をカッコつきで書く。 どのようにキャストすれば良いかが明確でない場合には、 コンパイラはエラーまたは警告を表示する。

課題 03.02
1 から 10 までの自然数の 2 乗を表示するプログラムを書け。 また、入力 n に対して 1 から n までの自然数の 2 乗を表示するプログラムを書け。

課題 03.03
0 から 4 まで 0.05 ずつ値を増やして、それに対する sin の値を表示するプログラムを書け。 ただし、組み込み関数 sin を用いるには sqrt と同様に libm を用いる必要がある。 また printf などの書式指定を工夫して、なるべく出力を見やすくしてみること。

課題 03.04
0.01 を 100 回足した値を出力するプログラムを書け。 またその値を整数にキャストして整数としても出力せよ。


ループ - while 文


for によるループは、予めループする回数などが定まっている場合に有効で、 そうでない場合には while によるループが多く用いられる。 while 文は与えられた条件が満たされている間、ループを行う。

次の例は自然数の素因数分解である。

 1  #include <stdio.h>
 2  
 3  int main(int argc, char *argv[])
 4  {
 5    int n, i;
 6
 7    printf("Input a positive integer : ");
 8    scanf("%d", &n);
 9  
10    while (n > 1) {
11      for (i = 2; i <= n; i++) {
12        if (n % i == 0) {
13          printf("%d\n", i);
14          n = n / i;
15          break;
16        }
17      }
18    }	 
19  
20    return 0;
21  }
10 行から 18 行がループで、n > 1 である間、操作を繰り返す。 ループ内では i = 2 から順番に割ってみて、割り切れたならば i を出力して n = n / i (C 言語の割り算) とする。 この際、for によるループを使っているが、一度割り切れたならば、それ以後の i で同じことをしてはいけないので、 直ちにこのループを抜けたい。 これを実現するのが 15 行目の break である。 break は for, while の両方に使うことができ、一番内側のループを直ちに終了させる。 この場合では for ループを抜けて while の次のループに移る訳である。 while (1) {...} というループを作って、抜けるときには常に break で抜ける、などという使い方もよく行われる。

for で書けるものは while でも書け、また逆も正しい。 for はループの条件が一ヶ所に固まっているため while よりも理解しやすい。 while は for よりも柔軟にループを作ることができる。 状況によって適宜使い分けるようにしよう。 また while によるループは継続条件が満たされ続ければ止まることなく無限ループになる。 この場合、unix 系の OS では Ctrl-C などで強制的に停止するしかない。 プログラムが確実に停止するように書くには、色々と注意が必要である。

課題 03.05
課題 03.02 のプログラムを while を使って書け。

課題 03.06
メニューを表示し、1 が入力されたら "good morning"、2 が入力されたら "good afternoon"、 と表示し再度メニューに戻り、 3 が入力されたら "bye" と表示してプログラムを終了するものを作れ。

課題 03.07
2 の平方根の近似値を求めるプログラムを書け。 (例えば以下の方法がある。 a=1, b=2 とする。a と b の平均を c = (a+b)/2 とする。 c2 > 2 ならば b = c とし、 c2 < 2 ならば a = c とする。 すると 2 の平方根は a と b の間にある。 これを繰り返すと c が近似値となる。)

課題 03.08
a0=0, a1=1 とし、帰納的に an+2=an+an+1 で数列 {an} を定める。 これをフィボナッチ数列という。 フィボナッチ数列の初めの n 項を出力するプログラムを書け。 (あとで説明する配列を用いてもよいが、配列を利用しないプログラムを考えよ。)

課題 03.09
入力された数値の合計と平均を求めるプログラムを以下の仕様で書け。

  1. はじめにデータの個数を入力し、その後データを入力する。
  2. 非負の実数をデータとし、負の数が入力された時に入力を終了する。

課題 03.10
与えられた自然数に対してコラッツの問題を検証するプログラムを書け。 (コラッツの問題とは以下のものである。「n を自然数とし、n が偶数ならば n/2、 n が奇数ならば (3n+1)/2 を考える。これを繰り返すといつかは必ず 1 になる」 ということは正しいか ?)


Previous Top Next