ようやく,この科目の本題「C言語」に入ろう. まず,これまでに勉強してきた言語 (Scheme,KTurtle,アルゴロジック) とは異なる部分について,理解しよう.
特に,根本的な違いは... KTurtle やアルゴロジックといったお遊び用の言語では, プログラムの実行結果が自動的に目に見える形 (タートルやロボットの動き)となって現れていた.
しかし,C等の本格的なプログラミング言語では, プログラムの実行結果は,基本的に, コンピュータの内部(メモリ)にデータとして記録されるだけだ. プログラマはメモリの様子を想像する必要がある. また,実行結果を見える形にするには, そのための指示(出力処理)も書いてやる必要がある.
Cのプログラムでは, さまざまな種類のデータを利用する. データは「値」だけでなく「型」を持ち, さらに,値は様々な形式で「表現」される. 今回は,これらの区別について理解しよう.
しばらく,C言語から離れていたので, 初回のページを軽く読み流してから, 今回の実習を始めよう. まず, 実習の準備をお忘れなく.
初回の計算機プログラム add.c では, 整数データを変数に代入し,その値を画面に表示していた.
今回は,まず List 1 のように, 変数には何も代入せず,表示だけしてみよう.
main() { int x, y, z; // 変数を宣言 printf("x=%d y=%d z=%d\n", x, y, z); // 変数の値を表示(%d は整数値に置き換わる) }
ここで,int x の部分では, 「このプログラムでは int 型の変数 x を使う」 と宣言している. (または,「変数 x を用意しておけ」と指示している.) しかし,変数の値については,明示していない.
このソースコードを編集しコンパイルし実行すると,何が表示されるか? 次の Unix コマンドで確かめてみよう:
$ vim var.c # エディタを起動 ... # List 1 を編集(コピペでOK) $ cc var.c -o var # コンパイル ... # もし,エラーが表示されたら,編集へ戻れ $ ./var x=-1081959192 y=14110708 z=1456448 # 意味不明な数値(場合により異なる)
このように,意味不明な数値が表示される. これらの数値は,メモリ内にたまたま残っていた無意味なデータであり, ゴミと呼ばれる.
変数値がゼロになっている場合もあるが,それは偶然にすぎない.
なお,たとえ話として「変数とは箱であり, 箱の中身が値である」と考えるとわかり易い場合もある. しかし,変数は現実の箱とは異なり, 空(カラ,empty)の状態になることは無い. 変数には常に,何らかの値が入っている. また,ゼロとカラとは別物.
したがって大抵の場合,変数を使うときには, 最初に定数を代入しておく必要がある. この作業を初期化と言う. では,初期化のコードを var.c に追加してみよう:
... int x, y, z; // 変数を宣言 x = 1; // x を 1 に初期化 y = z = 2; // y と z を 2 に初期化 ...
あるいは,次のように,宣言と初期化をまとめて書いてもよい:
int x = 1, y = 2, z = 2; // 宣言と初期化
しかし,次の書き方は NG:
int x = 1, y = z = 2; // エラー
では再度,コンパイルし実行しよう:
$ cc ...
$ ./var
x=1 y=2 z=2 # 初期化した通りの値が表示された
というわけで,変数を使う前には,必ず初期化しよう.
プログラミング言語によっては, すべての変数を自動的に 0 に初期化してくれるものもある. しかし,Cでは,必要に応じて, プログラマが変数を初期化しなければならない.
すべての変数を初期化しなければならない,のではない. たとえば,プログラムのユーザが値をキーボード入力する場合等では, わざわざ初期化しなくてよい. 入力が初期化を兼ねることになる.
int 型は, データ1個について 4 byte(4*8 = 32 bit)のメモリを使う. つまり,232 通り, −2,147,483,648 〜 +2,147,483,647 の範囲の整数を取り扱える.
多数桁の数値の表現について, ソースコードの記述や実行時の入力では, カンマ区切は使えない.
また,int 型のデータサイズは, 処理系(CPU や OS やコンパイラ)によっては, 4 byte ではない場合もある.
表現できる値の範囲の制限について確認するには, 初回の足し算プログラム add.c を改造し, 掛け算プログラム mul.c を作成すればよいだろう: (足し算 x + y を掛け算 x * y に変更するだけ. 以下,add.c の step 7 版を元にした実行例.)
$ ./mul 1000000 1000 1000000 * 1000 = 1000000000 # まぁ当然だ $ ./mul 1000000 3000 1000000 * 3000 = -1294967296 # えっ?
ここで,2 番目の実行例では, 正しい計算結果 3,000,000,000 は int 型の範囲外(3,000,000,000 > 2,147,483,647)なので, オーバーフローが発生し,不正な結果となっている.
ちなみに,ソースコードに整数を記述する場合, 10進数表現の他,16進数表現も利用できる. ソースコード内での記述例:
int a, b; a = 1023; // 10 進数として記述 b = 0x03FF; // 16 進数として記述
なぜ 16進数なんて使う必要があるのか? コンピュータの内部の様子を知るために必要.
現代のディジタルコンピュータは,内部的には, すべての数値を2進数表現して取り扱っている. しかし,2進数では桁数が大きくなりがちなので, 2進数4桁を16進数1桁に換算して表現すると, 人間にも取り扱い易くなる.
たとえば,10進数の 47 は,2進数では 10 1111, 16進数では 2F となる.
実数を取り扱うために, float 型(単精度浮動小数点)と double 型(倍精度浮動小数点) がある. 現代では普通,double 型の方を使う.
List 2 のプログラムを実行してみよう.
main() { double x = 1.234e+12; // 1.234×1012=1,234,000,000,000 double y = 5.0e-6; // 5.0×10-6=0.000 005 double a; a = x + y; printf("%f + %f = %f\n", x, y, a); // %f は実数値に置き換わる }
実行例:
$ ./double
1234000000000.000000 + 0.000005 = 1234000000000.000000 # えっ?
コンピュータによる実数計算は「完全に正確」ではない. 有効数字の制限から,必ず,誤差がつきまとう.
半角英数 1 文字のデータを扱う場合には, char 型の変数を使う. List 3 のプログラムを実行してみよう.
main() { char x = 'a'; char y = '\n'; char z = '%'; printf("x='%c' y='%c' z='%c'\n", x, y, z); }
実行結果:
x='a' y=' ' z='%'
ソースコード中で文字定数は, 引用符「'」で囲んで記述される. たとえば,'a' は,1 文字 a を表わす文字定数.
しかし,改行「\n」のように 2 文字以上によって表現される特殊な文字 (エスケープ系列)もある. これらは文字列ではなく 1 文字として取り扱われる.
ちなみに,char 型は, データの記憶のために 1 byte 分のメモリを使うので, 0 〜 255 とか −128 〜 +127 の範囲の整数についても表現可能. しかし,これでは実用上,範囲が狭すぎるので, 整数には int 型を使うのが普通.
複数の文字をまとめて取り扱うための文字列データについては, 後日説明する予定. ちなみに,二重引用符「"」で囲まれているものが 文字列定数である. (たとえば,既に使っている "Hello, world.\n" とか "x=%d y=%d z=%d\n" は文字列定数.)
これまでに利用して来た printf は, 一般に,次のような形式で記述される:
printf(書式文字列, データ, データ, ...);
printf の末尾の f は, ここに出てきた書式(format)のことを意味している. このことから printf は書式付き出力と呼ばれている.
書式文字列のほとんどの部分はそのまま表示されるが, 変換指定子(%d,%c,等の部分)は, データ(変数,定数,計算式,等の部分)の 値(計算結果)と置き換えられ, さらに,各指定子に固有の形式で表現されることになる. 変換指定子には次のようなものがある:
次のように,桁数の指定も可能:
printf("[%8.3f]\n", 1.0); // [ 1.000] と表示される(全8桁,小数3桁)
変換指定子についての詳しい説明は,教科書 7.2 にある. 今後,printf( ) を自由自在に使いこなせるよう, 各自で研究しておくこと.
List 4 は,0 〜 9 まで数える(10 回繰り返す)プログラムのソースである.
main()
{
int i; // 繰り返し回数のカウンタ
for (i = 0; i < 10; i++) {
printf("%d\n", i);
}
}
ここで,for ( ) の後の中括弧 { と } の間の処理が 10 回だけ繰り返される.
今回は,美しいソースコードの書き方として, インデント(indent; 字下げ)の方法をおぼえよう:
基本は,これだけ.
なお,インデントが無茶苦茶なソースでも コンパイルや実行には問題ない. しかし,必ず適切なインデントを心掛けること! インデントが適切であれば, プログラムが大規模になってきても, デバッグ(debug;プログラムの修正)が楽になる.
この授業では, インデントの乱れているプログラムは, たとえ,動作的に完全であったとしても, 完全であるとは認定されません. 「理解して作っていないので,動作したのは偶然にすぎない」 という考え方です. インデントの乱れは減点対象となります.
参考:インデントの作法
printf("%d\n", 'a');
printf("%c\n", 65);
ただし,for 文を使って, x を 0 〜 25 の間で変化させること.
実行例:
$ ./triangle 0: 1: * 2: ** 3: *** 4: **** 5: ***** . . . 17: ***************** 18: ****************** 19: *******************
なお,行番号について,桁数を揃えること.