0 で割り算をするプログラムを実行するとどうなるか、試しなさい。
C言語でくり返しを実行させる方法には三種類ありますが、 ここでは for と while だけを説明し、 do-while はとりあげません。 K&R2 では §1.2, 1.3, 3.5 を見てください。 do-while は §3.6 にあります。
for (文1; 式2; 文3) { ... }は次のように動きます。
もう一つの構文
while (式) { ... }は次のように動きます。
for も while も、中カッコの中(ループ本体)が一度も実行されない場合があります。
#include <stdio.h> /* ユーザが数を入力すると,1 からその数までの和を出力 */ main() { int i, n, sum; printf("いくつまで足しますか?\n"); scanf("%d", &n); sum = 0; for (i = 1; i <= n; i++) { sum = sum + i; } printf("1 から %d まで足すと %d です.\n", n, sum); }
i++ は「i を 1 だけ増やせ」、 i-- は「i を 1 だけ減らせ」の意味です。 それぞれ「i = i+1」「i = i-1」と書いても同じこと。 これらは等式としては正しくありませんが、 「i に i+1 を代入せよ」 「i に i-1 を代入せよ」の意味だからいいのです。 「sum = sum + i」も同様。 ++, -- については K&R2 §2.8 に述べられています。
上のプログラムで入力する数を 10, 100, 1000, 10000, ... のようにだんだん大きくしてゆくと、 どこかで int 型の限界を越えて答えがおかしくなります。 その場合でもエラーメッセージなどは一切出ません。 (やってみましょう。)
この例では for を使うほうが自然でしょうが、 次に while を使って書いてみました。
#include <stdio.h> /* ユーザが数を入力すると,1 からその数までの和を出力 */ main() { int i, n, sum; printf("いくつまで足しますか?\n"); scanf("%d", &n); sum = 0; i = 1; while (i <= n) { sum = sum + i; i++; } printf("1 から %d まで足すと %d です.\n", n, sum); }
次の例は while を使うほうが自然でしょう。
#include <stdio.h> /* アラレ数 */ main() { int n; printf("自然数を入れてください.\n"); scanf("%d", &n); while (n != 1) { if (n % 2 == 0) { /* n が偶数のとき */ n = n / 2; } else { /* n が奇数のとき */ n = 3 * n + 1; } printf("%d ", n); } }アラレ(霰)数とは
このプログラムは、ユーザが入力した数から始めて 1 になるまで、 この数列を出力します。
※ ここで中カッコが入れ子になった場合のインデントのしかたを説明する。 上のプログラムの while の部分で確かめてみるとよい。
main() { ... }のように書いても構わない。
#include <stdio.h> /* 自然対数の底 e の計算 */ main() { int n; double sum, kaizyo; sum = 1; kaizyo = 1; n = 1; for (n = 1; n < 24; n++) { kaizyo = kaizyo * n; sum = sum + 1 / kaizyo; printf("第 %d 項までの和は %.53f です.\n", n, sum); } }
自然対数の底 e を e = 1/0! + 1/1! + 1/2! + 1/3! + ... として計算しています。 C言語には階乗を表わす演算子はないので自力で計算しなければなりません。 ここでは n! を計算した結果を捨てないで次の (n+1)! の計算に使っています。
なお、「kaizyo = kaizyo * n」のところでは double 型と int 型を掛けています。 こういった場合、結果は double 型になります。 C言語ではこのあたりが煩雑です。 K&R2 §2.7, A6 に出ていますが、 とりあえずは int と double しか使わないので 「int と double を混ぜたら double になる」 とだけ覚えておけばよいでしょう。
※ 岩波「数学辞典第3版」1434 ページによれば e = 2.7182818284590452353... であり、 上のプログラムの計算結果は途中で違っている。 コンピュータの計算は近似計算なので、 むやみに桁数を多く表示させても意味がないときもある。
「かつ」や「または」は次のように書きます。 これらは for や while の条件部にも使えます。 K&R2 では §2.6 です。
if ((x > 0) && (x < 8)) { /* 「かつ」 */ ........ } if ((x < 0) || (x > 2)) { /* 「または」 */ ........ }なお、x > 0 などを囲んでいる小カッコは実は不要です。 これについての詳細は K&R2 §2.12 を参照してください。
※ 「かつ」も「または」も同じ文字を二つ打つことに注意。 まちがって「&」「|」をひとつだけ書いた場合、 それらにも意味があるのでコンパイラはエラーメッセージを出さない (かもしれない)。
次のプログラムは九九の表を出力します。 for を二重に使っていること以外は、 いままでに習ったことでわかると思います。
#include <stdio.h> main() { int i, j; for (i = 1; i <= 9; ++i) { for (j = 1; j <= 9; ++j) { printf(" %d", i*j); } printf("\n"); } }
「配列」というのは変数の列です。 K&R2 では §1.6 にあります。
#include <stdio.h> /* エラトステネスのふるいで 10000 以下の素数を全て求める */ #define UNERASED 0 /* ←英語として正しいかどうか */ #define ERASED 1 /* 責任はもてません :-) */ int a[10000+1]; /* 配列 a[0], ... , a[10000] */ main() { int n, i; for (n = 2; n <= 10000; n++) { /* 配列の初期化 */ a[n] = UNERASED; } for (n = 2; n <= 100; n++) { /* ふるいにかける */ for (i = 2; n * i <= 10000; i++) { a[n*i] = ERASED; } } for (n = 2; n <= 10000; n++) { /* 結果の出力 */ if (a[n] == UNERASED) { printf("%d は素数.\n", n); } else { printf("%d は合成数.\n", n); } } }
エラトステネスのふるい (Eratosthenes's sieve)を使い、2 以上 10000 以下の自然数について素数か合成数かを判定して出力するプログラムです。
数学科のみなさんには説明するまでもないでしょうが、 紙と鉛筆でやる場合はこうやります。 1 から 10000 までの自然数を書いておき、 まず 2 の倍数を斜線で消します。 次に 3 の倍数を消します。 次に 4 の倍数ですが、 これはすでに 2 の倍数として消されているので消さなくてよい……。 こうやって 100 の倍数まで消していって、 残ったものが素数でした。
int a[10000+1]; は 10001 個の int 型変数の配列 a[0], ... , a[10000] を宣言する構文です。 C言語の配列は必ず添字 0 から始まるので、 a[1] から a[10000] までを使うなら サイズ 10000 ではなくサイズ 10001 の配列が必要です。 int a[10001]; と書いてもいいのですが、 「a[0] があるから +1 が必要だよ」 ということを強調するためわざわざこう書きました。
そして、「斜線で消す」という代わりに、 a[5] に 0 が代入されていたら 5 は消されていない、 1 が代入されていたら消されている、としました。
#define で始まる二行ですが、 0 と 1 のどちらがどちらの印だったかわからなくなるといけないので、 こうやって名前をつけました。 #define にはこういう用法もあります。 この UNERASED と ERASED は変数名ではなく定数名なので大文字で書いています。
三つの for 文が何をやっているかは、 簡単にコメントしておいたので読めばわかると思います。 なお、ここでは合成数の倍数も消していますので、 このプログラムは最も効率のよいものではありません。
※ C言語では、配列の要素 a[i] の値を調べたり a[i] に代入したりするとき、 添字 i が正しい範囲にあるかどうかチェックしない。 もしも上のプログラムで a[20000] に代入したとしても、 コンパイル時にも実行時にもエラーメッセージは出ない。 しかし、 プログラムが暴走したりおかしな結果が出たりすることは起こり得るので、 絶対にそのようなプログラムを書いてはならない。
以下の練習問題をやる前に、 エラトステネスのふるいのプログラムで次の練習をしておくとよいでしょう。
出力が大量になり、最初の部分がよく見えない場合などは、 「./a.out | more」とします。 一画面分だけ出力されると「--続ける--」が出て止まりますので、 スペースバーを押せば次に進みます。 次に進まないでプロンプトに戻りたい場合は「q」を押してください。
全部やらなくても構いません。
1 から 1 まで足すと 1 です. 1 から 2 まで足すと 3 です. 1 から 3 まで足すと 6 です. 1 から 4 まで足すと 10 です. 1 から 5 まで足すと 15 です. 1 から 6 まで足すと 21 です. 1 から 7 まで足すと 28 です. 1 から 8 まで足すと 36 です. 1 から 9 まで足すと 45 です. 1 から 10 まで足すと 55 です.と出力されるようにせよ。
| 1 2 ... 9 ---+--------...---- 1 | 2 | : : 9 |のように被乗数・乗数を左と上に出力するようにしてみよ。
2 以上の自然数 n を一つ固定する。 “数”としては 0, 1, 2, ..., n-1 だけを考え、 足し算、掛け算は結果が n 以上になったら n で割った余りをもって答えとする。 この掛け算の表を出力するプログラムを書け。 n はプログラム中では「#define N 12」のようにし、 容易に変更できるようにせよ。 そして、いろいろな n に対しての出力結果を見ながら、 「一番上の行・一番左の列を除く各行・各列に 0, 1, 2, ..., n-1 がすべて現れるのは n がどのような数の場合か?」という数学の問題を考えてみよ。 この“数”を数学では Z/nZ, Z/n, Zn などと書き表す。 ただし、三つめの記号は別のものを表すこともあるのであまり勧めない。 (念のために注意:こうやっていろいろ試すのは実験である。 実験は証明の代用にはならないが、時には有用である。)