数字を入れておく箱

ここまでの段階でとりあえず、コンパイルができるようになって画面に文字が書けるようになった。

・・・コンピュータは「計算機」。残念ながら(?)計算らしいことはまだ、やってないわけで(でも内部では結構やってるぞ、あれでも)。 そういうわけでここでは「計算」を出来るようにしよう。計算とはいっても四則演算程度なんだけどね。 計算するとき、当然だけど変数が必要だ。そうすると変数って何?って話になってくる。

変数は変数である。中学1年のときに習うやつである。つまり、3x + 4 = 9とかの"x"のことである。ただ、「僕、まだ小学生なんだけど」という方もいらっしゃるかもしれないので書いておこう。というか、Cの変数の使い勝手は数学とは若干違うので中学校で数学の成績がよかった方も読んで欲しい。

変数とは簡単に言うと数字を入れておく「箱」のようなものである。さしあたりはこの理解でいこう。本当はうるさい薀蓄があるんだけどそれは後回し。

そう、広い意味での「箱」である。小物入れ、引越し用のダンボール箱、工具入れ、金庫、冷蔵庫、などである。

そしてそれぞれ、使い方ってものがある。冷蔵庫の中に工具をしまう奴はいないし、金庫に野菜をしまうこともまず、ない。というか、工具は冷蔵庫にしまっても実害はほとんどないけど野菜を金庫にいれておくと腐ってしまう。

それ以前の問題として突然、「これ、金庫にいれといて」と言われてもそもそも金庫がなければ困ってしまうわけで「どこに金庫があるよの!?」という文句が出てしまう。まず、「はい、ここに金庫があるよ」と言っておかなければならない。

同様の話がCの変数に当てはまるのだ。というわけで、まず、「箱」の種類の話からしよう。

箱の種類だけどC言語には大きく分けて四つある。

整数型

これは整数を入れておける箱である。もちろん、0以上の数も0以下の数も扱える。

文字型

文字型とあるが実際には整数を入れておける箱である。そもそもコンピュータは文字を文字としては理解していなく、各文字に対して数字が割り当てられているだけである。この型はそういう数字を扱うことを目的にしているという意味。・・・が、金庫に大金を入れてもボロキレを入れても問題ないのと同じ、実際には自由に使える。

単精度浮動小数点型

これは小数をいれておく箱である。「浮動」というのは例えば3.141592と314.1592のように小数点の位置がどこにきてもよいという意味だ(前に述べた通りだ)。

倍精度浮動小数点型

これは前述した単精度浮動小数点型よりもさらに精度を上げた浮動小数点型。ま、有効桁数(つまり仮数部の大きさ)が倍になっているってわけ。

実際にはこれに色々、形容詞をつけて大きめな整数型とか正の数のみの文字列型とかあるけどとりあえずはおいておこう。

質問コーナー

・・・つまり「くどい」こーなー。

分数はどうやって書くか。

小学校で習ったとおり、分数は小数で表せる。よって省略。ちなみに残念ながら循環小数を厳密に表す方法は(少なくとも簡単には)ない。それ以前の話でコンピュータ内部では二進法を使っているから・・・という薀蓄は少なくとも今はやめておこう。

複素数はどうやって表すか。虚数はどうやって表すか。

高校でならう二乗すると-1になるってやつ。結論から言うと単純に表す方法はない。普通は構造体とかを使って自分で書くのだがここではレベルを超えるので省略。これだけはFORTRANっていう化石みたいなプログラミング言語の方が上か。あ、gccは独自の機能で扱えるんだっけか。それに最近のC言語では公式にサポートされたとか。まあ、おいておこう。なんか、おいておくものが多いなあ(爆)。

書き方

で、実際にCで書く方法である。つまり、「こういう箱を用意してね」とお願いしておくのだ。ちなみにお願いしないで勝手に箱を使おうとすると「そんな箱、ないんですけど」って文句を言われてコンパイルが完了しないので注意。

  1. int hennsuu1;
  2. char hennsuu2;
  3. float hennsuu3;
  4. double hennsuu4;

1の書き方は「hennsuu1という名の箱を確保して頂戴、で、整数型ね」の意味。2は「hennsuu2という名の箱を文字型で確保してね」、3は「hennsuu3を確保しろ。単精度浮動小数点型だ!」、4は「hennsuu4をかくほしてちょ、倍精度浮動小数点型でネ」の意味。とりあえずこの先、整数型とか書くのが面倒なので以後、例えばint型、みたいな感じで書くことにする。

ところでその「箱」の名前だけど一応規則みたいのがある。長さは31文字以内。名前に使えるのはアルファベットの大文字小文字、アンダーバー"_"、あと数字のみ(一文字目は駄目)のみである。よって#$%!#&)なんてのは変数の名前として不適切だ。

また、実際問題、aとかtmpとかという変数名は絶対にやめよう。絶対、わけがわからなくなる。変数名にどんな名前を使おうが長かろうがプログラム自体の実行速度やサイズには全く関係ない。わかりやすい名前を使うべし。

さらに次のような書き方もある。

  1. int val1, val2;
  2. int val3 = 0;

1はval1、val2の二つのint型を同時に宣言した例。このようにコンマで区切って続けて書くことも出来る。2はval3を確保し、その値を0にした例。こういうのを初期化という。ちなみに初期値はコンパイルする前から定まっていないといけない。例えばこんなのはだめ。

int val1 = 3, val2 = 5;
int val3 = val1 + val2;

こういうことをしたいのなら初期化ではなく、

int val1 = 3, val2 = 5;
int val3;

val3 = val1 + val2;

みたいに書く必要があるわけだ。実はこの例、「そんなの意味ないじゃん」と思うかもしれないけど後で関数とかの話が出てきたときにもっといい例があるのでそのときに述べよう。

ところでそれ以前の問題として「え、val3 = 0って当然じゃないの?変数の中って最初は0に決まっているでしょ。」って思わない?実はこれ、非常に危ないバグを撒こうところだ。Cでは変数を宣言した段階では中身は決まっていなく、もちろん0である保障もない。いうならば買ってきたばかりの冷蔵庫、あけてみたらカビだらけ、みたいな感じなんだ。なので確保したばかりの変数の中が勝手に0であるなんて思い込んでプログラムを組むと間違いなく嵌る。要注意!!!

なので、初めっから0がいいのであれば2で示すように初期化をしないといけない。「何て面倒な規格なんだろう」と思うなかれ。C言語は一に速度、二に速度なので足を引っ張るようなことは自動的には一切やらないように出来ている。初期化するほうが初期化しない場合に比べて速いのだ。たしかに冷蔵庫を買ってきてそのまま使った方が掃除してから使うより早く使えるだろう。そして冷蔵庫もはじめっから中も見ずにカビが生えていないことにしたら本当に生えていたときに困るだろう。Cもそんな感じだ。ただ、Cではほぼ間違いなく、適当な値が入っている。

では、実際に変数をちょっとだけ使ったプログラムを書いてみる。

1	#include <stdio.h>
2
3	/* 四則演算 */
4	int main(void)
5	{
6		int val1 = 6, val2 = 2;
7		int add, sub, multi, divide;
8
9		add = val1 + val2;
10		sub = val1 - val2;
11		multi = val1 * val2;
12		divide = val1 / val2;
13
14		printf("%d + %d = %d\n", val1, val2, add);
15		printf("%d - %d = %d\n", val1, val2, sub);
16		printf("%d * %d = %d\n", val1, val2, multi);
17		printf("%d / %d = %d\n", val1, val2, divide);
18
19		return 0;
20	}

L6(6行目と書くのは面倒いのでこう書くよ)ではval1とval2をint型で確保、それぞれ6と2に初期化。L7にadd, sub, multi, divideを確保、初期値は不定。

L9はval1とval2を加え(+)、addに代入(=)する。ちなみにCで=とは右辺の式の結果を左辺に代入の意味だ。

L10はval1からval2を引き(-)、subに代入(=)する。

L11はval1とval2を掛け(*)、multiに代入(=)する。

L10はval1をval2で割り(/)、divideに代入(=)する。

L14~L17は結果を画面に表示する部分だ。ここでもprintfを使っているが前回のHello, world!!とはちょっと違う。 例えばL14を見よう。まず、"%d + %d = %d\n"とある。%dとはある整数を表示するとの意味。なので表示結果は

[整数] + [整数] = [整数]

となる。ところで、整数って具体的に何よ、ってことになるけどそれが後ろに続いているval1, val2, addなわけだ。つまり、この順番でそれぞれの%dに対応しているわけ。なので結果は

[val1の値] + [val2の値] = [addの値]

となる。わかった?あとも同様だ。

そういうわけで実行結果はこうなるはず。

6 + 2 = 8
6 - 2 = 4
6 * 2 = 12
6 / 2 = 3

ところでval1の初期値を6ではなく、7にしてみよう。結果はどうなるだろうか。おそらくこうなる。

7 + 2 = 9
7 - 2 = 5
7 * 2 = 14
7 / 2 = 3

あれ、7 / 2って3だっけ?違うな。実際に電卓で計算すると3.5である。おかしい。間違っている。Cは嘘つきだ!

・・・落ち着け。では、その辺をもうちょっとじっくり見るため、割り算のところだけを取り出してみた。

割り算の謎

で、ちょっとだけ変更、こんな感じ。

1	#include <stdio.h>
2
3	/* わりざん1 */
4	int main(void)
5	{
6		int val1 = 7, val2 = 2;
7		int divide;
8		int remain;
9
10		divide = val1 / val2;
11		remain = val1 % val2;
12
13		printf("%d / %d = %d\n", val1, val2, divide);
14		printf("あまり %d\n", remain);
15
16		return 0;
17	}

で、補足。11行目に%というのが現れているがこれは割り算のあまりを求める方法だ。つまり、val1をval2で割り、その余り(%)をremainに代入(=)する。実際に実行するとこうなる。

7 / 2 = 3
あまり1

ちなみに日本語が表示できない環境だと文字化けするので注意。

たしかに7 / 2は3、あまり1だ。めでたしめでたし・・・

「ねえ、それでもいいんだけど、7 / 2 = 3.5ってどうやったら得られるのよ」

実はこの話、真面目なことを言うと薀蓄が結構ある。なので一歩ずつ詰め寄ろう。で、またプログラムをシンプルに書き換える。

1	#include <stdio.h>
2
3	/* わりざん2 */
4	int main(void)
5	{
6		int val1 = 7, val2 = 2;
7		int divide;
8
9		divide = val1 / val2;
10
11		printf("%d / %d = %d\n", val1, val2, divide);
12
13		return 0;
14	}

で、これの実行結果は当然これだ。

7 / 2 = 3

まず、真っ先におかしいと思うのは7行目だろう。divideはint型と言っている。int型変数には整数は入るが小数は入らない相談だ。だから小数以下が消えて整数部分だけ残っているわけだ。感覚としてはコップに氷水を入れるのは問題ないが紙袋に氷水をいれると水だけ出て、氷だけ残る、そういう感じだ。明らかにこれではまずい。

そういうわけでこういう風に変えてみる。

1	#include <stdio.h>
2
3	/* わりざん2 */
4	int main(void)
5	{
6		int val1 = 7, val2 = 2;
7		double divide;
8
9		divide = val1 / val2;
10
11		printf("%d / %d = %f\n", val1, val2, divide);
12
13		return 0;
14	}

7行目をdouble型にしてみた。これで小数が表現できる。あとはprintfの部分でdivideを受ける部分を%fにした。%dは整数を意味したが%fは浮動小数を意味するわけでこれでprintfでも小数表示に対応した。で、実行してみる。

7 / 2 = 3.000000

・・・え゛?

やっぱりCは嘘つきだ。たしかの小数で結果が表示された。けどやっぱり3じゃないか?

・・・ここから先、恐らくCプログラマー初心者が嵌りやすい部分なのだ。僕も最初は意味不明でしたよ、はい。

では、とりあえず騙されたと思って次のようなプログラムを組んでみる。

1	#include <stdio.h>
2
3	/* わりざん3 */
4	int main(void)
5	{
6		double val1 = 7.0, val2 = 2.0;
7		double divide;
8
9		divide = val1 / val2;
10
11		printf("%f / %f = %f\n", val1, val2, divide);
12
13		return 0;
14	}

今度はval1、val1もdouble型で扱うようにしてみた。"7"とは書かずに"7.0"と書いたのは倍精度浮動小数点であるからだ。もし、val1がfloat型なら"7.0f"と書くのが正しい。 ・・・が、実際には"="は適切な型変換が行われるのでこの場合は単に"7"と書いても特に問題はないはずだ。val1、val2はdouble型になったので11行目も適当に%fに変えた。

で、実行してみる。

7.000000 / 2.000000 = 3.500000

おおおおおおおおお!きたきたきたきた
キターーーーーーーーーーーーーー

いや、失礼。そう、なんとval1やval2もdouble型にするとうまくいったのだ。

もう一つ、こういう例も。val1だけdouble型、val2はint型にした。もちろん、printfの中も相応に書き換え書き換え・・・

1	#include <stdio.h>
2
3	/* わりざん4 */
4	int main(void)
5	{
6		double val1 = 7.0;
7		int val2 = 2;
8		double divide;
9
10		divide = val1 / val2;
11
12		printf("%f / %d = %f\n", val1, val2, divide);
13
14		return 0;
15	}

で、結果は

7.000000 / 2 = 3.500000

おお、これでも出来た!実は反対にval1をint型、val2をdouble型にしても出来るのだ。試してみよう。

これはどういうことか。種明かしをしよう。

Cは演算(割り算に限らず)する段階でどういう型で演算をしているかを考える必要があるのだ。単に「箱」の型だけでなく、「演算している最中」の型だ。

val1 / val2

とあった場合、ざっくり言うと変数の条件が厳しい型をもって演算されることになっているのだ。

もう、わかっただろう、何故、/* わりざん2 */でdivideだけをdouble型にしても意味がない理由が。そう、=で代入する以前に演算結果がすでにint型だったからなのだ。何故ならval1もval2もint型だったからだ。いくら紙袋ではなくコップであっても、もともと中に入れたものが氷だけだったらコップに入っても氷水にはならないのと同じだ(とりあえず、氷は融けないとしよう)。

しかしそうなると別の問題が出てくる。

「int型どうしの割り算の結果では小数が表示できないのかよ。」

そこでキャストの出番である。これは「とりあえず、今だけは別の型で扱ってくれよ」という意味である。

1	#include <stdio.h>
2
3	/* わりざん5 */
4	int main(void)
5	{
6		int val1 = 7, val2 = 2;
7		double divide;
8
9		divide = (double)val1 / val2;
10
11		printf("%d / %d = %f\n", val1, val2, divide);
12
13		return 0;
14	}

6行目で示すとおり、val1もval2もint型だ。注目すべきは9行目。

"(double)val1"は今だけval1をdouble型として扱ってくれという意味。これのお陰で"val1 / val2"はdouble型/int型となり、より厳しいdouble型で演算してくれるわけだ。ちなみに7行目で示すとおり、演算結果を受け取る「箱」はちゃんとdouble型だ。実行すると

7 / 2 = 3.500000

となるはず。これで満足だろう?

ちなみに世の中には7行目をご丁寧に

(double)val1 / (double)val2

とかやる人もいるけど見通しが悪くなるだけなのでやめよう。意味がない。さっきも述べたとおり、演算子の両側の数で厳しい方の型にあわせてくれるんだから。何もval1、val2両方ともdouble型にすることはない。

初めての変数、いかがだろうか。少し、敢えて曖昧な言葉を使って濁した部分があるがこんな感じだ。次も変数に関して少し薀蓄を語ろうかと思う。後半のところで使った「より厳しい型」の具体的な意味、あと前半で少し述べた型につけられる形容詞について、だ。

質問コーナー

・・・つまりあなたの混乱を少しでも解こうとするが結局、理屈っぽく説明しているだけのコーナー。

double val1 = 2.0;
って書いても、
double val1 = 2;
ってval1の形に合わせて代入されるんだよね、"2.0"なんて面倒な書き方、何のために存在するの?というか、使う場面ってあるの?
答えは簡単だ。以下のコードを実行してみな。
1	#include <stdio.h>
2
3	/* 変数を使わないで割り算 */
4	int main(void)
5	{
6		double divide1, divide2, divide3;
7
8		divide1 = 7 / 2;
9		divide2 = 7.0 / 2.0;
10		divide3 = 7.0 / 2;
11
12		printf("7 / 2 = %f\n", divide1);
13		printf("7.0 / 2.0 = %f\n", divide2);
14		printf("7.0 / 2 = %f\n", divide3);
15
16		return 0;
17	}
結果はもちろん、こうだ。
7 / 2 = 3.000000
7.0 / 2.0 = 3.500000
7.0 / 2 = 3.500000

もう、理由は説明するまでもないな。L8はint型同士の割り算だから演算中もint型だけでやっているのだ。L9はdouble型同士、L10は一方はdouble型、というわけでいずれにしてもこれらはdouble型で演算されるわけだ。というわけで、こういう結果になった。

一つ指摘しておこう。たしかに代入するだけの場合は"7"と書いても"7.0"と書いても問題ない。けど、計算式の中では重要な意味を持つのだ。というわけでソースを書くときは変数だけでなく、ただの数字もどういう型なのかをちゃんと考えておくべし。

くわしすぎるC