2012 年度「計算数学」 2012-10-19

§3.1 if ... else による分岐

(3.1.1) if ... else を使った自明なプログラム。

#include <stdio.h>

main() {
    int x;

    x = 1;      /* この数を変えて試せ */

    if (x == 0) {
        printf("その数は零です.\n");
    } else if (x > 0) {
        printf("その数は正です.\n");
    } else {
        printf("その数は負です.\n");
    }
}

(3.1.2) if の行以降については次を見よ。


    if (条件1) {
        文1
    } else if (条件2) {    
        文2
    } else if (条件3) {
        文3
    } else {
        文4
    }
  • 条件1が真ならば文1だけを実行する。
  • 条件1が偽で条件2が真なら文2だけを実行する。
  • 条件1も条件2も偽で条件3が真ならば文3だけを実行する。
  • 条件1も条件2も条件3も偽なら文4だけを実行する。
   (文のうち一つだけが実行されることに注意。)

(3.1.3) if が一つしかなく,かつ,else がないとき。


    if (条件1) {           
        文1
    }
  • 条件1が真ならば文1を実行する。
  • 条件1が偽ならば何も行なわない。

(3.1.4) K&R2 では §3.2, §3.3 を参照。 そこでは省略可能な中カッコを省略しているが, 私のやり方のように全てつけておくほうが間違いが少ない。 インデントについても上の例でしっかり覚えよ。 中カッコの中の文はタブ一つ分だけ余計に字下げする。

(3.1.5) if (...) の中で使える記号は、数学上の記号と少々異なる。

数学上の記号
C言語での記号 <><= >===!=
注意: 「=<」「=>」は不可。 「if (x = 0)」,
0 < x < 8」は別の意味。

(3.1.6) 「if (a+b < c+d)」のように if の小カッコ内で計算をしてから比較することもできる。

(3.1.7) K&R2 では §2.6, §A7.9, §A7.10 を参照のこと。

(3.1.8) 「かつ」は if (x > 0 && x < 8), 「または」は if (x < 0 || x > 2) { と書く。 「&」はシフトしながら 6のキーを、 「|」はシフトしながら のキーを打つ。 K&R2 では §2.6 を見よ。 「かつ」も「または」も同じ文字を二つ打つことに注意。 まちがって「&」「|」をひとつだけ書いた場合, それらにも意味があるのでコンパイラはエラーメッセージを出さない (かもしれない)。

(3.1.9) 練習問題その1.(3.1.1) のプログラムで, x を 0 とし、 else if の行を次のように変えたら,出力はどうなるか?

    } else if (x >= 0) {

(3.1.10) 練習問題その2.(3.1.1) のプログラムで、 if (x = 0) と書いたらどうなるか。 0 をほかの数に変えての実験もしてみよ。

(3.1.11) 練習問題その3.「1 から 3 までの整数のうち,好きなものを入力してください」 と出力し,ユーザが入力した数に応じて, 「1 を選んだあなたは**な人です」というような文章を出力する, “占い”プログラムを作れ。 「1 リンゴ,2 ミカン,3 バナナ のうち,好きな果物の番号を入力してください」 などとしてもよい。 1, 2, 3 以外の数を入力した場合も,何かメッセージを出すとよい。

§3.2 for によるループ

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

(3.2.2) for を使った自明なプログラムは,(2.4.2) を見よ。

(3.2.3) for の働きをきちんと説明すれば,以下のとおり。

    for (文1; 式2; 文3) {     
        ...
    }
  • 文1(初期設定)を実行。(最初に一度だけ。)
  • 式2(継続条件)が成り立つかどうかチェックする。
    • 式2が真ならば中カッコの中を実行。 その後,文3(再初期化)を実行し,式2のチェックにもどる。
    • 式2が偽ならば,中カッコはとばして次へ進む。

※ 中カッコの中(ループ本体)が一度も実行されないこともありえる。

おおまかに理解するなら, 「文1から始めて,式2である限り,毎回文3としながら, くり返す」となる。(2.4.2) の例では, 「i イコール 0 から始めて,i が 100 未満である限り, 毎回 i を 1 ずつ増しながらくり返す」となる。

(3.2.4) i++ は「変数 i の値を 1 だけ増せ」, i-- は「変数 i の値を 1 だけ減らせ」の意味。

(3.2.5) 前回の (2.4.2) のプログラムについて, for 文だけをくわしく説明する。 まず初期設定 i = 1 を実行。 次に継続条件 i < 100 をチェックする。 ここでは 1 ≦ 100 だから成り立っている。 よって中カッコの中を実行。 再初期化 i++ が実行されて i の値は 2 に変わる。 そして継続条件 i < 100 のチェックに戻る。 また成り立っているので中カッコの中を実行。 再初期化 i++ が実行されて i の値は 3 に変わる。 これをくり返してゆくと, i は 99 まで,1 ずつ増されてゆく。 99 のときも中カッコの中は実行されるが, 次に再初期化 i++ を行なうと i の値は 100 になる。 すると今度は継続条件 i < 100 が成り立たないので, 中カッコはとばして次へ進む。

(3.2.6) 式2の書き方は, if の後ろの小カッコの中の書き方と全く同じ。

(3.2.7) 上限の 100 に深い意味はない。 いろいろ変えて動かしたい場合は,次のように書く。 #define N 100 は, 「プログラム中では N は定数 100 として扱え」の意味。 この 100 を書き換えれば,上限が変わる。 書き換えるべき箇所がプログラムの先頭付近にくるので,便利である。

#include <stdio.h>
 
#define N 100
 
main() {
    int i;
 
    for (i = 0; i <= N; i++) {
        printf("%d の二乗は %d です.\n", i, i*i);
    }
}

(3.2.8) 上のプログラムで「#define N」の右に書く数をだんだん大きくしてゆくと, やがて int 型の限界を越えて答えがおかしくなる。 その場合でもエラーメッセージなどは一切出ない。試せ。

(3.2.9) N の値を大きくして実行すると, 「これは時間がかかりすぎだ,失敗!」と思うときがあろう。 プログラムの実行を中止してプロンプトに戻りたいときは Ctrl+C を押す。 途中でいったんとめて様子をみるときは Ctrl+S を押す。 任意のキーで再開。 (Ctrl+Q でなければ再開しないこともある。)

(3.2.10) 練習問題その1:(3.2.7) のプログラムを改変し, i の値が N から 0 まで,1 ずつ減りながら出力されるようにせよ。

(3.2.11) 練習問題その2:(3.2.7) のプログラムを改変し, i の値が 2 ずつ増えるようにせよ。 (文3は「i = i+2」となる。 「いまの i の値に 2 を加えたものを i に代入せよ」 の意味。(2.4.3) の sum = sum + n; も同様である。)

§3.3 配列

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

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

(3.3.3) 配列を使った自明なプログラム。

#include <stdio.h>
 
int a[10];      /* 配列の宣言。これで a[0], ..., a[9] が使える */
 
main() {
    int i;
 
    for (i = 0; i < 10; i++) {      /* 配列の要素に代入 */
        a[i] = i * i;
    }
    for (i = 0; i < 10; i++) {      /* 配列の要素を印字(=出力) */
        printf("a[%d] は %d です.\n", i, a[i]);
    }
}

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

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

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

(3.3.7) 練習問題1.上のプログラムの配列の大きさを,(3.2.7) で説明した #define を利用して書け。 三か所に現れる N を一度に変えることができて便利である。

(3.3.8) 練習問題2.(3.3.3) のプログラムを改変し, a[i]i*i を代入したあと, 隣あった項どうしの差 (a[1] - a[0], a[2] - a[1], ..., a[N-1] - a[N-2]) を出力するプログラムを書け。 出力時に,添え字が範囲外の値にならないよう注意せよ。

§ 3.4 中カッコの入れ子

(3.4.1) 中カッコが入れ子になった場合のインデントの(一つの)やり方は, 以下の通りである。

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

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

(3.4.3) 練習問題その1:(2.4.5) のプログラムを,素数のみを出力するよう改変してみよ。 素数を単に並べて出力すればよい。 2 3 5 7 11 13 ... のように。

§3.5 乱数

(3.5.1) 「乱数」が数学でどう定義されるかは,ここでは考えない。 計算機でいう乱数とは, 種(たね)と呼ばれる整数を元に漸化式を使って発生させる, 乱数のように見える数列のことである。

(3.5.2) 関数 rand() は,小カッコの中には何も書かずに呼び出す。 呼び出されるたびに, 0 以上 RAND_MAX 以下の整数(int 型)に値をとる乱数の項を, 一つずつ返す。

#include <stdio.h>
#include <stdlib.h> /* rand() */

main() {
    int i;

    for (i = 0; i < 10; i++) {      /* 10 項からなる乱数列を発生 */
        printf("%d\n", rand());
    }
}

このプログラムは, 一行に一つずつ,全部で 10 個の整数を出力するが, これらの並びが乱数,というわけである。

※ 2行目。関数 rand() を使うには stdlib.h#include することが必要である。 どの関数を使う際にどの .h ファイルが必要になるのかは, この授業ではそのつど教える。また,K&R2 の付録に書いてある。 「/* rand() */」は, 「rand() を使うためにこの行を書いた」という覚え書きである。

(3.5.3) RAND_MAX はコンパイラごとに違う値を取り得る定数なので, 次のプログラムで調べよ。

#include <stdio.h>
#include <stdlib.h> /* RAND_MAX */

main() {
    printf("%d\n", RAND_MAX);
}

興味のある人は値をメモしておこう。

(3.5.4) 上の (3.5.2) のプログラムは,毎回同じ乱数列を出力する。 それでは困るので,現在時刻を乱数の種(たね)にすることを考える。

#include <stdio.h>
#include <stdlib.h> /* rand(), srand() */
#include <time.h>   /* time() */

main() {
    int i;

    {   /* この中カッコの中(乱数の種の設定)はわからなくてもよい */
        unsigned seed = (unsigned)time(NULL);   /* 現在時刻を取得して */
        printf("乱数の種は %u です.\n", seed);
        srand(seed);                            /* それを乱数の種に */
    }

    for (i = 0; i < 10; i++) {
        printf("%d\n", rand());
    }
}

センターの Linux でこのプログラムを動かすと, 現在時刻の秒未満は無視されるようである。

(3.5.5) ※ 種の値を出力させるのは,再現性のためである。 上のプログラムが出力した種を次のプログラム (乱数の種の設定の部分以外は省略)に入力すれば, 全く同じ乱数列が得られる。

    {   /* この中カッコの中(乱数の種の設定)はわからなくてもよい */
        unsigned seed;

        printf("乱数の種を入力してください.\n");
        scanf("%u", &seed);
        srand(seed);                            /* それを乱数の種に */
    }

(3.5.6) rand() % 100 とすると, 0 以上 100 未満の整数に値をとる乱数が得られる。 厳密に言えば,RAND_MAX の値によっては, 0 の出現率と 99 の出現率はわずかながら異なるが, この授業ではそこまでは気にしないことにしよう。

(3.5.7) 練習問題:(3.5.4) のプログラムを,(3.5.6) のように改造し, 0 以上 100 未満の整数に値をとる乱数が得られること, および,実行するごとにその乱数列が異なることを確認せよ。


岩瀬順一