前章では関数について説明しましたが、関数と変数の関係は説明しませんでした。
ここでは、カウント関数を作り、それを元に関数と変数の関係を説明します。
カウント関数とは、呼び出された回数を数える関数のことです。
特にそのような用語があるわけではありませんが、便宜上そのように呼びます。
単純に考えれば、それはかなり簡単に実現出来ます。
変数を用意して、それに毎回1足していけば良いだけのことです。
次のプログラムは、上記の考えをそのまま実現した例です。
このプログラムの実行結果は、次の通りになります。#include <stdio.h> int countfunc(void); int main(void) { countfunc(); countfunc(); countfunc(); return 0; } int countfunc(void) { int count = 0; /* 初期化 */ count++; printf("%d\n",count); return count; }
変数countに1足す関数を3回呼び出したはずなのに、答えは全て1です。
1
1
1
そこで、あえて警告を無視して、変数を初期化せずに使ってみたいと思います。
この場合、初めにどんな数字が出るかわかりませんが、
それでも、2回目の呼び出しでは前回より1多く、3回目は2多く出るはずです。
次のプログラムは、変数countを初期化しない例です。
このプログラムの実行結果は、(筆者の環境では)次のようになりました。#include <stdio.h> int countfunc(void); int main(void) { countfunc(); countfunc(); countfunc(); return 0; } int countfunc(void) { int count; /* 初期化なし(本当はやってはいけない) */ count++; printf("%d\n",count); return count; }
2つ目、3つ目の数字共に、何故か全く同じ結果になってしまいました。
5369
5369
5369
これには、変数の寿命が深く関係しています。
実は、関数の中で宣言された変数は、関数が終了すると捨てられてしまうのです。
再び同じ関数が呼び出された場合、もう一度変数を作り直します。
この時、前回とは別のメモリが使われるので、値も変わってしまいます。
先ほどの場合、筆者の環境で実行させた時は偶然同じ値が出てきたようですが、
これも、結局の所全く偶然の産物であり、同じ値が出てくるかはわかりません。
つまり、関数の中で宣言された変数の寿命は関数の中だけであるということです。
この様に、関数の中で寿命が終わる変数を、ローカル変数と呼びます。
また、変数の寿命や有効範囲などをまとめて、スコープと呼ぶことがあります。
[ ローカル変数 ]
関数の中で宣言された変数。仮引数の変数も同様。
その関数が終わると捨てられ、
再度関数が呼ばれた時には新しく作られる。
また、他の関数から使用することは出来ない。
[ スコープ ]
識別子が有効な範囲を制限する仕組みの総称。
一般的には、変数の寿命と有効範囲を決定する仕組み。
ローカル変数は関数が終わると破棄されてしまうなら、
main関数で宣言しておけば、main関数が終わるまでは生き残るはずです。
次のプログラムは、main関数でもcount変数を宣言した例です。
このプログラムの実行結果は、(筆者の環境では)次のようになりました。#include <stdio.h> int countfunc(void); int main(void) { int count = 0; countfunc(); countfunc(); countfunc(); return 0; } int countfunc(void) { int count; /* 初期化なし(本当はやってはいけない) */ count++; printf("%d\n",count); return count; }
main関数でもcount変数を宣言したにもかかわらず、結果は変わりません。
5369
5369
5369
これは、変数の有効範囲が深く関係しています。
ローカル変数が有効なのは、宣言された関数の中のみです。
別の関数で同じ名前の変数が宣言されていても、それは別の変数となります。
従って、main関数内の変数の値を変えても、他の関数の変数には影響がありません。
ここまで、関数毎に、変数は全く別々に作られていることがわかりました。
どうして、この様な仕組みになっているのかと言いますと、
その理由は、関数の独立性を高めるためなのです。
前章では、min〜maxの合計を求めるsum関数を作りましたが、
この関数は、他のプログラムでもすぐに使えます。
この様に、関数を簡単に使えるようにするには、各関数が独立している必要があります。
例えば、sum関数では、num変数を使用していました。
もしも、全ての関数で変数を共有するように作られていた場合は、
他の関数でnum変数を使っていた場合、その変数の値が変更されてしまいます。
従って、sum関数を他のプログラムで使用する場合には、
事前に同じ名前の変数がないか、全て調べておかなければなりません。
しかし、各関数で使われる変数が独立していれば、
他の関数の変数に影響を与える心配は一切ありません。
変数の値が突然変更されるという恐怖から開放されるのです。
[ 古典的言語では ]
古典的な言語(HSPも含め)にはこの仕組みがありません。
その為、変数の値がどこかで勝手に変更されないよう、
プログラム全体に注意をはらわなければなりません。
その為、大規模な開発は大変困難です。
これまで、ローカル変数の寿命を関数内と説明しましたが、正確にはブロック内です。
ブロックとは、{}で囲まれている範囲のことを指しています。
次のプログラムのように、関数内で意図的にブロックを作ることも可能です。
このプログラムの実行結果は、次の通りになります。#include <stdio.h> int main(void) { int value1 = 10; int value2 = 20; printf("1:value1 %d\n",value1); printf("1:value2 %d\n",value2); { int value1; value1 = 30; value2 = 40; printf("2:value1 %d\n",value1); printf("2:value2 %d\n",value2); } printf("3:value1 %d\n",value1); printf("3:value2 %d\n",value2); return 0; }
注目部分は2つあります。
1:value1 10
1:value2 20
2:value1 30
2:value2 40
3:value1 10
3:value2 40
もう1つは、意図的に作ったブロックの中でも value2 が使えることです。
value2 はブロック中で変更した結果が、3回目の表示にも残っています。
if文やfor文の {} もブロックなので、同じことが可能なのですが、
1つの関数の中に同じ名前の変数がたくさんあるとややこしくなります。