変数のスコープとは、その変数を参照することの出来る範囲のことです。スコープに応じて、それぞれ変数の呼び方が異なります。
ローカル変数*1 とは、関数(手続き)の内部で宣言された変数のことを指します。サンプルをいくつかあげます。
procedure MyProc; var Num: Integer; // ローカル変数 Num を宣言 s: string; // ローカル変数 s を宣言 begin //... end; function MyFunc: Integer; var Value: Integer; // ローカル変数 Value を宣言 begin //... end;
上の MyProc( ) 内で宣言された変数 Num, s および MyFunc( ) 内で宣言された変数 Value のことをローカル変数と呼びます。
ローカル変数は、一番スコープが狭いです。ローカル変数は、それが宣言された関数(手続き)、イベントハンドラ内でのみ参照できます。
procedure SayHello; var Greeting: string; // ローカル変数 Greeting を宣言 begin Greeting := 'Hello'; // ローカル変数に値を代入する ShowMessage(Greeting); // ローカル変数に格納されている値を表示する end;
手続き SayHello( ) では、ローカル変数 Greeting を宣言し、手続き内で値の代入、表示を行っています。このローカル変数 Greeting は、SayHello( ) 内でのみ参照することが出来ます。つまり、変数 Greeting のスコープは SayHello( ) 内のみ、ということですね。
ここで間違ったサンプルを示すことで、「ローカル変数とはどういったものなのか」を理解するのに助けになるかもしれません。結局、ローカル変数とはその名の通り'ローカル'な変数という意味で、外部(つまり、それが宣言された関数の外)からは参照できませんよ、ということだけです。
procedure DisplayGreeting; begin // SetGreeting 関数内で宣言されている // ローカル変数 Greeting を参照する. これは出来ない!! ShowMessage(Greeting); end; procedure SetGreeting(s: string); var Greeting: string; begin Greeting := s; end; procedure SayHello; begin SetGreeting('Hello'); DisplayGreeting; end;
上のプログラムをコンパイルすると、「未定義の識別子: 'Greeting'」とコンパイルエラーが表示されます。これはコンパイラが「手続き DisplayGreeting で Greeting というもの(識別子)を参照してるけど、そんなものは見当たらないよ」と文句を言っているわけです。プログラマにしてみれば、(意図としては)SetGreeting で宣言している Greeting を参照したいと考えているのかもしれませんが、コンパイラには、そんなことはわかりません。なぜなら、SetGreeting( ) で宣言された変数 Greeting のスコープは、SetGreeting( ) 内のみですので、それ以外の関数(手続き)から、つまり DisplayGreeting( ) からは見えないのですから。
関数の内部で宣言された変数は、その関数内でしか参照できません。ですから、2 つの異なる関数の内部で同じ名前の変数が宣言されたとしても、それらは全く異なったものとなります。
procedure MyProc1; var Num: Integer; begin Num := 1200; ShowMessage(IntToStr(Num)); end; procedure MyProc2; var Num: Integer; begin Num := 30; ShowMessage(IntToStr(Num)); end; procedure TForm1.Button1Click(Sender: TObject); begin MyProc1; MyProc2; end;
MyProc1( ) を呼び出すと "1200" と表示され、MyProc2( ) を呼び出すと "30" と表示されます。MyProc1( ) と MyProc2( ) で宣言されている Integer 型の変数は、どちらも Num という名前が付いていますが、これらは全く異なった変数です。共通点は名前が同じということだけで、MyProc1( ) で変数 Num に値 1200 を代入したからといって、MyProc2( ) で宣言されている変数 Num にも1200 が代入されるということはありません。
以上のことをまとめると次のようになります。
これは、次のように言い換える事も出来ます。
グローバル変数は、ローカル変数よりも大きなスコープを持ちます。例えば、次のプログラムではグローバル変数 Book をそれぞれ DisplayBook1, DisplayBook2, DisplayBook3 の 3 つの手続きから参照しています。
implementation {$R *.dfm} var Book: string = 'Delphi プログラミングバイブル'; // グローバル変数 Book procedure DisplayBook1; begin ShowMessage(Book); end; procedure DisplayBook2; begin ShowMessage(Book); end; procedure DisplayBook3; begin ShowMessage(Book); end; procedure TForm1.Button1Click(Sender: TObject); begin DisplayBook1; DisplayBook2; DisplayBook3; end;
ローカル変数は、それが宣言された関数(手続き)の中でしか参照できませんでしたが、グローバル変数ではスコープが大きくなり、参照できる箇所が広くなります。また、プログラムからも分かりますように、グローバル変数は関数の外側で宣言します。
上のサンプルプログラムのようにグローバル変数には初期値を設定できます(ローカル変数では出来ません)。初期値の設定はオプションです。初期値を省略した場合、 グローバル変数は、それが string であればカラに、Integer であれば 0 に、ポインタであれば、nil に初期化されます。
この節で取り上げたいことは、「グローバル変数 Book は *implementation 部で宣言されている」という点です。implementaion 部で宣言されたグローバル変数は、ユニットグローバルな変数です。ユニットグローバルとは、そのグローバル変数のスコープが(その変数が宣言された)ユニット内に限定されているという意味です。
まとめると次のようになります。
ある変数が複数のユニットから参照される必要があるなら、「真のグローバル変数」を使用します。
まず断っておきたいのですが、「真のグローバル」という言葉は、筆者が勝手につけた名前です。ユニットグローバルとの違いを強調する為に、ここではあえて「真のグローバル変数」と呼ぶことにします。大事なのは呼び方ではなく、ユニットグローバルな変数とどこが違うのかという点を学ぶことです。
真のグローバル変数は、どこからでも参照できる一番スコープの広い変数です。ユニットグローバルとの違いは、真のグローバル変数が宣言されてあるユニット以外のユニットからも参照できるかどうかという点です。*2
真のグローバル変数は、ユニットの interface 部で宣言します。
Unit2
unit Unit2; interface var Greeting: string = 'Hello'; implementation end.
Unit1
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} uses Unit2; procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(Greeting); end; end.
まとめると次のようになります。
細かいことですが、「implementation 部で宣言されたグローバル変数は、そのユニット内から参照することが出来る」という表現は、若干ですが正しくはありません。それは、次のプログラムで確認できます。
implementation {$R *.dfm} procedure SayGreetting; begin ShowMessage(Greeting); // エラー!! 未定義の識別子: Greeting end; var Greeting: string = 'Hello';
より正確には、「implementation 部で宣言されたグローバル変数は、そのグローバル変数が宣言された行よりも後の行以降でアクセスできる」となります。
*1 局所変数とも呼ばれます。
*2 ただし、参照したい真のグローバル変数が宣言してあるユニットを、参照する側の uses 節に指定する必要があります。uses 節については、uses 節、interface 部、implementation 部を参照してください。