(21.1) 中カッコが入れ子になっている例が出てきた。 その場合のインデントの(一つの)やり方は以下の通りである。
(21.2) これらの規則はすぐに慣れることができるし、 これらを守ってさえいれば、「ある中カッコとペアになる中カッコをさがす」 ことや「ある文を囲む最小の中カッコをさがす」ことはきわめて容易である。 前にも言ったが、 自分流のインデントをすでに編み出している人はそれで構わない。 そうでない人はまずはこれを覚えよ。 また、プログラムを書き上げてから最後にインデントを整えるのではなく、 きちんとインデントしながら書く習慣をつけるとよい。 そのほうが、すでに書いた部分をよりよく理解できるので、 より整理された頭でプログラムを書き進めることができる。
(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) 上のプログラムは、配列の大きさを N として、 次のように書くほうがよい。 配列の大きさを変えることが容易になるからである。 実際に変えて実行してみよ。
#include <stdio.h> #define N 10 int a[N]; /* 配列の宣言。これで a[0], ..., a[N-1] が使える */ main() { int i, n; for (i = 0; i < N; i++) { /* 配列の要素に代入 */ a[i] = i * i; } for (i = 0; i < N; i++) { /* 配列の要素を印字(=出力) */ printf("%d\n", a[i]); } }
(22.8) 自然対数の底 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 項は %.53f です.\n", n, a[n]); } s[0] = 1; /* 部分和の計算 */ for (n = 1; n < N; n++) { s[n] = s[n-1] + a[n]; printf("第 %2d 項までの和は %.53f です.\n", n, s[n]); } }
(22.9) 「%.0f」とすると、小数点以下は 0 ケタ表示する。 すなわち、小数点以下を表示しない。 「%.53f」は小数点以下 53 ケタ。
(22.10) ※ センターの linux 上のCコンパイラでは、 第 3 項までの和 1+1+1/2+1/6 = 2+2/3 の小数点以下十数ケタ目ですでに違っている。 よって、 上のプログラムの計算結果は小数点以下十数ケタ目あたりまでしか信頼できない。 コンピュータの計算は近似計算なので、 むやみに桁数を多く表示させても意味がないときがある。 53 ケタ目まで表示させたのはそのことを知ってもらうためにすぎない。 (岩波「数学辞典第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.1) 「かつ」や「または」は次のように書く。 これらは for の継続条件にも使える。 K&R2 では §2.6 を見よ。
if ((x > 0) && (x < 8)) { /* 「かつ」 */ ........ } if ((x < 0) || (x > 2)) { /* 「または」 */ ........ }x > 0 などを囲んでいる小カッコは実は不要である。 これについての詳細は K&R2 §2.12 を参照。 「0 < x < 8」はC言語でも意味のある式だが、 数学とは意味が異なるので上の意味では使えない。
(23.2) 「かつ」も「または」も同じ文字を二つ打つことに注意。 まちがって「&」「|」をひとつだけ書いた場合、 それらにも意味があるのでコンパイラはエラーメッセージを出さない (かもしれない)。
(24.1) 第一項、第二項が 1 である Fibonacci 数列 (漸化式 an+2 = an+1 + an を満たす数列) の最初の十数項を出力するプログラムを書け。 隣り合った項の比も出力するようにできるとさらによい。 (その場合は全て double 型変数にするのが簡単である。 この比は (-1 + 51/2)/2 = 0.618033... に収束する。)
(24.2) a[ ] を適当な大きさの配列とする。 a[i] に i の二乗を代入し、 次に、その階差数列を出力するプログラムを書け。