2009 年度「計算機基礎論3B」 2009-10-23

§19 for によるループ

(19.1) C言語でくり返しを実行させる方法には三種類あるが、 ここでは for だけを説明し、 whiledo-while はとりあげない。 K&R2 では §1.2, §1.3, §3.5 を見よ。 do-while は §3.6 にある。

(19.2) for の働きをきちんと説明すれば、以下のとおり。
    for (文1; 式2; 文3) {     
        ...
    }
  • 文1(初期設定)を実行。(最初に一度だけ。)
  • 式2(継続条件)が成り立つかどうかチェックする。
    • 式2が真ならば中カッコの中を実行。 その後、文3(再初期化)を実行し、式2のチェックにもどる。
    • 式2が偽ならば,中カッコはとばして次へ進む。
※ 中カッコの中(ループ本体)が一度も実行されないこともありえる。

(19.3)

#include <stdio.h>  /* ユーザが数を入力すると,1 からその数までの和を出力 */

main() {
    int i, n, s;

    printf("いくつまで足しますか?\n");
    scanf("%d", &n);

    s = 0;

    for (i = 1; i <= n; i++) {
        s = s + i;
    }
    printf("1 から %d まで足すと %d です.\n", n, s);
}

(19.4) for 文のみ説明する。n には 10 が代入されているとしよう。 まず初期設定 i = 1 を実行。 次に継続条件 i <= n をチェックする。 ここでは 1 ≦ 10 だから成り立っている。 よって中カッコの中を実行。 s の値は 0 だったから i の値 1 を加えると 1 に変わる。 再初期化 i++ は「i を 1 だけ増やせ」の意味である。 これが実行されて i の値は 2 に変わる。 そして継続条件 i <= n のチェックに戻る。 また成り立っているので中カッコの中を実行。 s の値は 3 に変わる。 これをくり返してゆくと、 i は 10 まで、1 ずつ増えながら s に足されてゆく。 そのときの s の値が 1+2+…+10 である。 次に再初期化 i++ を行なうと i の値は 11 になる。 すると今度は継続条件 i <= n が成り立たないので、 中カッコはとばして次の printf() の行へ進む。

(19.5) 「i--」と書くと「i を 1 だけ減らせ」の意味になる。 K&R2 §2.8 参照。

(19.6) s = s + i; は等式としては正しくないが、 この「=」は代入の意味であるから問題ない。

(19.7) 上のプログラムで入力する数を 10, 100, 1000, 10000, ... のようにだんだん大きくしてゆくと、 どこかで int 型の限界を越えて答えがおかしくなる。 その場合もエラーメッセージは出ない。

§20 練習問題

(20.1) 次のプログラムは、 誤差の関係で、想像されるようには動かない。 (想像してから動かしてみよ。 %f%.64f に変え、 小数点以下 64 けたまで表示させると、この謎は解ける。)
#include <stdio.h>

main() {
    double x;

    for (x = 0; x < 1; x = x + 0.1) {
        printf("x の値は %f です.\n", x);
    }
}

§21 インデントの(一つの)しかた

(21.1) 中カッコが入れ子になっている例が出てきた。 その場合のインデントの(一つの)やり方は以下の通りである。

  1. {」を打ったらその直後で改行し、 次の行は前の行の(空白以外の)頭よりもタブ一つ分(ここでは空白四文字分) 下げて始める。 (ただし、コメントは「{」の右に書いても構わない。)
  2. }」を打つときは、 それに対応する「{」を含む行の(空白以外の)頭の文字とそろえる。
  3. その他の行の頭は前の行とそろえる。

(21.2) これらの規則はすぐに慣れることができるし、 これらを守ってさえいれば、「ある中カッコとペアになる中カッコをさがす」 ことや「ある文を囲む最小の中カッコをさがす」ことはきわめて容易である。 前にも言ったが、 自分流のインデントをすでに編み出している人はそれで構わない。 そうでない人はまずはこれを覚えよ。 また、プログラムを書き上げてから最後にインデントを整えるのではなく、 きちんとインデントしながら書く習慣をつけるとよい。 そのほうが、すでに書いた部分をよりよく理解できるので、 より整理された頭でプログラムを書き進めることができる。

§22 配列

(22.1) 変数名には、a, b, x, y のような一文字はもちろん、sum, total のように二文字以上からなる文字列も使える。 英語の単語でなくてももちろん可。 x11 のように数字がはいっても構わないが、 数字で始まる 1x などは不可。 (使おうとしたらどうなるか?)

(22.2) x1, x2, x3 とあったら人間は関連のある変数だろうと想像するが、 コンパイラにとっては全く無関係な、ただの三つの変数である。

(22.3) 「配列」(昔の書き方では「排列」)とは、 0 からある自然数までの整数で添え字づけられた有限個の変数 x[0], x[1], x[2], ..., x[N-1] を一斉に定義し、一括して扱うものである。 K&R2 では §1.6 を参照。

(22.4) 配列を使った自明なプログラムの例を示す。

#include <stdio.h>

int a[10];      /* 配列の宣言。これで a[0], ..., a[9] が使える */

main() {
    int i, n;

    for (i = 0; i < 10; i++) {      /* 配列の要素に代入 */
        a[i] = i * i;
    }
    for (i = 0; i < 10; i++) {      /* 配列の要素を印字(=出力) */
        printf("%d\n", a[i]);
    }
}

(22.5) 「配列の宣言」の行。 こう書くと a[0] から始まる十個の int 型変数が使える。 すなわち、a[0], a[1], a[2], ..., a[9] が使える。 a[10] は使えない。うっかり使ってしまいやすいので注意。

(22.6) 「配列の宣言は main() の中カッコの外で行ない、 ほかの変数の宣言は main() の中カッコの中で行なう」 という規則があるわけではないが、 とある事情により、結果として、この授業ではこうすることが多い。 だからこれが規則だと思っておけばよい。 正確なところは K&R2 §1.10, §4.3 参照。

(22.7) 自然対数の底 e を e = 1/0! + 1/1! + 1/2! + 1/3! + ... として計算するプログラムである。 階乗は、C言語には用意されていないので、自力で計算しなければならない。 配列を用いなくても書けるのだが、 このほうが書きやすいなら、これでもよい。 (階乗を double 型に入れているのは、 あとの計算が double 型であることと、 int 型よりも double 型のほうが格納できる整数の範囲が広いからである。 このような判断は、いまは自分でできなくても構わない。)

#include <stdio.h>  /* 自然対数の底 e の計算、配列版 */

#define N 23

double f[N];    /* 階乗の値を入れる配列 */
double a[N];    /* 各項の値を入れる配列 */
double s[N];    /* 部分和の値を入れる配列 */

main() {
    int n;

    f[0] = 1;                       /* 階乗の計算 */
    for (n = 1; n < N; n++) {
        f[n] = f[n-1] * n;
    }
    for (n = 1; n < N; n++) {
        printf("%2d の階乗は %.0f です.\n", n, f[n]);
    }
    for (n = 1; n < N; n++) {       /* 各項の計算 */
        a[n] = 1 / f[n];
    }
    for (n = 1; n < N; n++) {
        printf("第 %2d 項は %.64f です.\n", n, a[n]);
    }
    s[0] = 1;                       /* 部分和の計算 */
    for (n = 1; n < N; n++) {
        s[n] = s[n-1] + a[n];
        printf("第 %2d 項までの和は %.64f です.\n", n, s[n]);
    }
}

(22.8) 「#define N 23」は、 「以下、N が出てきたら定数 23 で置き換えよ」 の意味だった。この定数が 23 だと第 22 項まで計算する。 どこまで計算するかを変えたくなったら、ここだけ書き換えればよいから、便利である。

(22.9) 「%.0f」とすると、小数点以下は 0 けた表示する。 すなわち、小数点以下を表示しない。 「%.64f」は小数点以下 64 けた。

(22.10) ※ センターの Linux 上のCコンパイラでは、 第 3 項までの和 1+1+1/2+1/6 = 2+2/3 の小数点以下十数けた目ですでに違っている。 よって、 上のプログラムの計算結果は小数点以下十数けた目あたりまでしか信頼できない。 コンピュータの計算は近似計算なので、 むやみに桁数を多く表示させても意味がないときがある。 64 けた目まで表示させたのはそのことを知ってもらうためである。 (岩波「数学辞典第3版」1434 ページによれば e = 2.7182818284590452353... である。)

(22.11) ※ (センターの Linux での)上のプログラムの出力を見ると、 第 18 項から第 22 項までの値は 0 でない。 しかし、「第 17 項までの和」以降はすべて同じ値になる。 すなわち、0 でない値を加えているにもかかわらず値が変わらない。 近似計算なので、こういうことも起こるのである。

(22.12) ※ C言語では、配列の要素 a[i] の値を調べたり a[i] に代入したりするとき、 添字 i が正しい範囲にあるかどうかチェックしない。 「int a[10];」と宣言したうえで、 もしも誤って a[10] に代入したとしても、 コンパイル時や実行時にエラーメッセージが出るとは限らない。 しかし、 プログラムが暴走したりおかしな結果が出たりすることは起こり得るので、 絶対にそのようなプログラムを書いてはならない。

§23 「かつ」と「または」

(23.1) 「かつ」や「または」は次のように書く。 これらは for の継続条件にも使える。 K&R2 では §2.6 を見よ。

    if (x > 0 && x < 8) {       /* 「かつ」 */
        ........
    }

    if (x < 0 || x > 2) {       /* 「または」 */
        ........
    }
※ 「0 < x < 8」はC言語でも意味のある式だが、 数学とは意味が異なるので、使うな。

(23.2) 「かつ」も「または」も同じ文字を二つ打つことに注意。 まちがって「&」「|」をひとつだけ書いた場合、 それらにも意味があるのでコンパイラはエラーメッセージを出さない (かもしれない)。

§24 練習問題

(24.1) 第一項、第二項が 1 である Fibonacci 数列 (漸化式 an+2 = an+1 + an を満たす数列) の最初の十数項を出力するプログラムを書け。 隣り合った項の比も出力するようにできるとさらによい。 (その場合は全て double 型変数にするのが簡単である。 この比は (-1 + 51/2)/2 = 0.618033... に収束する。)

(24.2) a[ ] を適当な大きさの配列とする。 a[i]i の二乗を代入し、 次に、その階差数列を出力するプログラムを書け。


岩瀬順一