宣言と文(Delphi)
基本的な構文要素:インデックス への移動
このトピックでは、Delphi の宣言と文の構文について説明します。
uses 句(やユニットの各部を分ける implementation などの予約語)を除くと、プログラムはすべて宣言と文から構成され、それらはブロックにまとめられています。
このトピックでは、以下の事項について説明します。
- 宣言
- 代入などの単純な文
- 条件テスト(たとえば、if-then、case)、反復処理(たとえば、for、while)などの構造化文
目次
宣言
変数、定数、型、フィールド、プロパティ、手続き、関数、プログラム、ユニット、ライブラリ、およびパッケージの名前は、識別子と呼ばれます。26057 のような数値定数は識別子ではありません。識別子は、使用する前に宣言する必要があります。例外は、コンパイラが自動的に認識する定義済みのいくつかの型、ルーチン、および定数と、関数ブロックでの変数 Result、およびメソッドの実装での変数 Self だけです。
宣言は識別子を定義し、必要であれば識別子のためのメモリを割り当てます。以下に例を示します。
var Size: Extended;
これは Extended 型(実数)の値を格納する Size という変数を宣言します。
function DoThis(X, Y: string): Integer;
これは 2 つの文字列を引数として受け取って整数を返す DoThis という関数を宣言します。宣言はすべてセミコロンで終わります。複数の変数、定数、型、またはラベルを一度に宣言するときは、適切な予約語を 1 回だけ記述する必要があります。
var
Size: Extended;
Quantity: Integer;
Description: string;
宣言の構文と配置は、定義する識別子の種類によって異なります。一般に、宣言はブロックの先頭か、ユニットの interface セクションまたは implementation セクションの先頭(uses 句の後)にのみ記述できます。変数、定数、型、関数などを宣言する場合の固有の規約については、それぞれのマニュアルに記載されています。
ヒント指令
"ヒント" 指令の platform、deprecated、および library は、宣言の後ろに記述できます。これらの指令は、コンパイル時に警告を生成します。ヒント指令は、型宣言、変数宣言、クラス、インターフェイス、構造体の宣言、クラスまたはレコード内のフィールド宣言、手続き、関数、メソッドの宣言、およびユニット宣言に適用できます。
ヒント指令がユニット宣言内にある場合は、そのヒントがユニット内のすべてに適用されます。たとえば、Windows 上で Windows 3.1 スタイルの OleAuto.pas ユニットは使用しないように推奨されています。このユニットまたはユニット内のシンボルへの参照は、すべて非推奨のメッセージを生成します。
シンボルまたはユニットに対する platform ヒント指令は、そのシンボルまたはユニットが存在しない可能性があること、またはプラットフォームによって実装方法がかなり異なる可能性があることを示します。シンボルまたはユニットに対する library ヒント指令は、コードが存在しない可能性があること、またはライブラリのアーキテクチャによって実装方法がかなり異なる可能性があることを示します。
platform および library 指令は、プラットフォームまたはライブラリの種類を指定しません。プラットフォームに依存しないコードを記述することが目的の場合は、シンボルがどのプラットフォームに固有であるのかを知る必要はありません。シンボルが一部のプラットフォームに固有であるとマークされているだけで十分です。これにより、移植性に関してそのシンボルが問題を引き起こす可能性があることを知ることができます。
手続きまたは関数の宣言の場合、ヒント指令と残りの宣言の間をセミコロンで区切ります。以下に例を示します。
procedure SomeOldRoutine; stdcall; deprecated;
var
VersionNumber: Real library;
type
AppError = class(Exception)
...
end platform;
ソース コードが {$HINTS ON} {$WARNINGS ON} 状態でコンパイルされる場合、これらの指令を使って宣言された識別子への参照により、適切なヒントまたは警告が生成されます。特定の動作環境(Windows など)に固有の項目を示すには platform を使用し、使われなくなった項目つまり下位互換性のためにのみサポートされている項目を示すには deprecated を使用し、特定のライブラリまたはコンポーネントのフレームワークに依存することを示すには library を使用します。
Delphi コンパイラは、ヒント指令 experimental も認識します。この指令を使用して、不安定な開発状態のユニットを識別します。そのユニットを使用するアプリケーションをビルドした場合、コンパイラは警告を生成します。
Delphi のヒント指令に関する詳細については、「メソッド宣言での警告指令」をを参照してください。 すべての Delphi 指令は、「指令」に一覧されています。
文
文はプログラム内のアルゴリズム的処理を定義します。代入や手続き呼び出しなどの単純な文を組み合わせて、ループや条件文、その他の構造化文を構成できます。
ブロック内の複数の文およびユニットの初期化部と終了処理部の複数の文を区切るには、セミコロンを使用します。
単純文
単純文とは、他の文を含まない文です。単純文には、代入、手続きや関数の呼び出し、および goto ジャンプがあります。
代入文
代入文は次の形式をとります。
variable := expression
ここで、variable は変数や変数の型キャスト、逆参照ポインタ、構造化変数の要素などの変数参照です。 expression は、代入互換の式です(関数ブロック内では、variable は、定義されている関数の名前と置き換えができます。 手続きと関数(Delphi))。 := シンボルは、代入演算子と呼ばれることもあります。
代入文は、変数の現在の値を式の値で置き換えます。以下に例を示します。
I := 3;
これは 3 という値を変数 I に代入します。代入文の左辺の変数参照は、右辺の式でも使用できます。以下に例を示します。
I := I + 1;
これは I の値をインクリメントします。その他の代入文の例を次に示します。
X := Y + Z;
Done := (I >= 1) and (I < 100);
Hue1 := [Blue, Succ(C)];
I := Sqr(J) - I * K;
Shortint(MyChar) := 122;
TByteRec(W).Hi := 0;
MyString[I] := 'A';
SomeArray[I + 1] := P^;
TMyObject.SomeProperty := True;
手続きと関数の呼び出し
手続き呼び出しは、手続き名と、必要な場合はパラメータ リストによって構成されます。手続き名には限定子を付ける場合と付けない場合があります。次に例を示します。
PrintHeading;
Transpose(A, N, M);
Find(Smith, William);
Writeln('Hello world!');
DoSomething();
Unit1.SomeProcedure;
TMyObject.SomeMethod(X,Y);
{$X+} で拡張構文が有効になっている場合、関数呼び出しは、手続きの呼び出しと同様に独立した文として扱うことができます。
MyFunction(X);
このように関数を使用する場合、戻り値は破棄されます。
手続きと関数の詳細は、「手続きと関数 (Delphi)」を参照してください。
goto 文
goto 文の形式は次のとおりです。
goto label
goto 文は、指定したラベルでマークされた文へプログラムの実行を移します。文をマークするには、あらかじめラベルを宣言する必要があります。ラベルを宣言したら、マークする文の先頭にラベルとコロンを付けます。
label: statement
ラベルは次のように宣言します。
label label;
複数のラベルを一度に宣言することもできます。
label label1, ..., labeln;
ラベルには任意の有効な識別子または 0 ~ 4294967295 の番号を使用できます。
ラベルの宣言、マークされた文、および goto 文は、同じブロックに存在している必要があります。詳細は、以下の「ブロックとスコープ」を参照してください。このため、ジャンプによって手続きや関数の中に入ったり手続きや関数から出たりすることはできません。同一のブロックの複数の文を同じラベルでマークしてはなりません。
以下に例を示します。
label StartHere;
...
StartHere: Beep;
goto StartHere;
これは Beep 手続きを繰り返し呼び出す無限ループになります。
さらに、ジャンプによって try-finally または try-except 文の中を出入りすることはできません。
構造化プログラミングでは、goto 文の使用は一般に推奨されていません。ただし、次の例のように、ネストしたループから抜け出る方法として使用されることがあります。
procedure FindFirstAnswer;
var X, Y, Z, Count: Integer;
label FoundAnAnswer;
begin
Count := SomeConstant;
for X := 1 to Count do
for Y := 1 to Count do
for Z := 1 to Count do
if ... { some condition holds on X, Y, and Z } then
goto FoundAnAnswer;
... { Code to execute if no answer is found }
Exit;
FoundAnAnswer:
... { Code to execute when an answer is found }
end;
goto はネストしたループから出るために使用していることに注意してください。ループまたはその他の構造化文に goto を使って入ってはなりません。予期しない結果が生じる可能性があります。
構造化文
構造化文は他の文で構成されている文です。複数文を順に実行、条件付きで実行、繰り返し実行する場合は、構造化文を使用します。
- 複合文と with 文は、それを構成する文を順に実行します。
- 条件文(if 文または case 文)は、指定された判断基準により、構成する文を 1 つ実行します。
- 繰り返し文(repeat、while、または for ループ)は、それを構成する文を繰り返し実行します。
- いくつかの特殊な文(raise、try...except、および try...finally 構文)は、例外の生成と処理を実行します。 例外の生成と処理の詳細は、「例外(Delphi)」を参照してください。
複合文
複合文は一連の単純文または構造化文で構成され、これらの文が記述されている順で実行されます。複合文は、予約語 begin で始まり、予約語 end で終わります。複合文を構成する文はセミコロンで区切られます。以下に例を示します。
begin
Z := X;
X := Y;
X := Y;
end;
end の前の最後のセミコロンはオプションです。したがって、これは次のように記述することもできます。
begin
Z := X;
X := Y;
Y := Z
end;
複合文は、Delphi の構文によって単一の文が要求される場合に必要です。プログラム、関数、および手続きのブロックのほかに、条件文やループなどの構造化文でも使用します。以下に例を示します。
begin
I := SomeConstant;
while I > 0 do
begin
...
I := I - 1;
end;
end;
1 つの文だけで構成される複合文を記述することもできます。複雑な式のかっこと同様に、begin と end を使用することによってあいまいさが解消され、判読性が向上する場合があります。文をまったく含まない複合文を使用して、何も実行しないブロックを作成することもできます。
begin end;
with 文
with 文は、レコードのフィールドや、オブジェクトのフィールド、プロパティ、およびメソッドを参照するための簡便な方法です。with 文の構文を次に示します。
with obj do statement
または
with obj1, ..., objn do statement
obj は、レコード、オブジェクト インスタンス、クラス インスタンス、インターフェイス、またはクラス型(メタクラス)インスタンスへの参照を表す式であり、statement は任意の単純文または構造化文です。statement の中では、obj のフィールド、プロパティ、およびメソッドを、識別子だけを使って参照できます。つまり、限定子は不要です。
たとえば、次のように宣言されているとします。
type
TDate = record
Day: Integer;
Month: Integer;
Year: Integer;
end;
var
OrderDate: TDate;
これに対して、with 文を使用して、次のようなコードを記述することができます。
with OrderDate do
if Month = 12 then
begin
Month := 1;
Year := Year + 1;
end
else
Month := Month + 1;
あるいは、with 文を使用しないで、次のようにコードを記述することもできます。
if OrderDate.Month = 12 then
begin
OrderDate.Month := 1;
OrderDate.Year := OrderDate.Year + 1;
end
else
OrderDate.Month := OrderDate.Month + 1;
obj の解釈に配列のインデックス付けやポインタの逆参照が必要な場合は、複合文が実行される前にそれらの処理が 1 回だけ実行されます。このため、with 文は簡潔であるだけでなく効率も優れています。また、このため、with 文の実行中は文内部での変数への代入によって obj の解釈が影響を受けることはありません。
with 文内の変数参照やメソッド名は、可能な限り、指定されたオブジェクトまたはレコードのメンバとして解釈されます。同じ名前を持つ別の変数やメソッドに with 文からアクセスする場合は、次の例のように限定子を前に付ける必要があります。
with OrderDate do
begin
Year := Unit1.Year;
...
end;
with の後に複数のオブジェクトまたはレコードを指定すると、文全体が一連のネストした with 文のように扱われます。そのため、次の宣言は
with obj1, obj2, ..., objn do statement
これは、次と同じ意味です。
with obj1 do
with obj2 do
...
with objn do
// statement
この場合、statement の中の変数参照やメソッド名は、可能な限り objn のメンバとして解釈されます。解釈できない場合は、可能な限り objn1 のメンバとして解釈され、以下同様に続きます。同じ規則が obj 自体の解釈にも適用されます。たとえば、objn が obj1 と obj2 の両方のメンバである場合には、obj2.objn と解釈されます。
with 文では操作対象の変数またはフィールドが必要なため、この文をプロパティに対して使用するときには注意が必要です。with 文は、操作対象の変数を参照によって使用できると想定しています。
with を使用するときに特に注意しなければならない点は以下のとおりです。
- with は、読み取り専用プロパティを読み取るだけにしか使用できません。現れたレコードまたはオブジェクトのフィールドを変更しようとすると、コンパイル エラーが発生します。
- プロパティでフィールドに対する書き込みアクセスが認められている場合でも、やはり with を使ってフィールドを変更することはできません。
次のコードは、レコードを表す読み取り専用プロパティに対して with 文を使用する場合の問題を示す例です。次のクラスがあるとします。
TShape = class
private
FCenter: TPoint;
public
property Center: TPoint read FCenter;
end;
この TPoint は、次のように宣言されたレコードです。
TPoint = record
X, Y: Integer;
end;
通常、Center プロパティは読み取り専用であり、FCenter フィールドの値やフィールドを変更することはできません。 この場合、次のように with 文を使用するとコンパイル エラーが発生します。Shape.Center は変数でないので、その参照を使用できないためです。
with Shape.Center do
begin
X := 100;
Y := 100;
end;
注意が必要なのは、読み取り/書き込みプロパティに対して with 文を使う場合です。元の TShape クラスを次のように変更して、FCenter フィールドに書き込みアクセスができるようにします。
TShape = class
private
FCenter: TPoint;
public
property Center: TPoint read FCenter '''write FCenter''';
end;
Center プロパティが読み取り専用でないにも関わらず、同じコンパイル エラーが出力されます。この問題を解決するには、コードの次の部分を変更します。
with Shape.Center do
begin
X := 100;
Y := 100;
end;
次のようなコードに変更します。
{ Copy the value of Center to a local variable. }
TempPoint := Shape.Center;
with TempPoint do
begin
X := 100;
Y := 100;
end;
{ Set the value back. }
Shape.Center := TempPoint;
if 文
if 文には 2 つのの形式、if...then と if...then...else があります。if...then 文の構文を次に示します。
if expression then statement
expression は Boolean 値を返します。expression が True である場合は statement が実行され、それ以外の場合は実行されません。以下に例を示します。
if J <> 0 then Result := I / J;
if...then...else 文の構文を次に示します。
if expression then statement1 else statement2
expression は Boolean 値を返します。expression が True である場合は statement1 が実行され、それ以外の場合は statement2 が実行されます。以下に例を示します。
if J = 0 then
Exit
else
Result := I / J;
then および else 句にはそれぞれ 1 つの文が含まれますが、これらの文には構造化文も使用できます。以下に例を示します。
if J <> o then
begin
Result := I / J;
Count := Count + 1;
end
else if Count = Last then
Done := True
else
Exit;
then 句と else の間にはセミコロンを使用しないことに注意してください。if 文全体の後でセミコロンを使用して、ブロック内の次の文と区切ることはできます。ただし、then 句と else 句の間には、スペースまたは改行以外に何も必要はありません。if 文で else の直前にセミコロンを記述することは、よくあるプログラミング エラーです。
ネストした if 文に関して特殊な問題が発生する場合があります。この問題の原因は、if 文に else 句があるものとないものがあり、しかもこれらの 2 種類の文の構文が else 句の有無を除いて同じであることです。一連のネストした条件文で else 句が if 文より少ない場合は、どの else 句がどの if に結び付くのかが明らかではない場合があります。たとえば次のような文があるとします。
if expression1 then if expression2 then statement1 else statement2;
この文は、次の 2 とおりの方法で解析できます。
if expression1 then [ if expression2 then statement1 else statement2 ];
if expression1 then [ if expression2 then statement1 ] else statement2;
しかし、コンパイラは常に 1 番目の方法で解釈します。次に実際のコードで記述します。
if ... { expression1} then
if ... {expression2} then
... {statement1}
else
... {statement2}
これは、次と同じ意味です。
if ... {expression1} then
begin
if ... {expression2} then
... {statement1}
else
... {statement2}
end;
ネストした条件文の解析は最も内側の条件文から行われ、else はすべて左側の最も近い if に結び付けられます。この例を 2 番目の方法でコンパイラに解釈させるには、次のように明示的に記述する必要があります。
if ... {expression1} then
begin
if ... {expression2} then
... {statement1}
end
end
else
... {statement2};
case 文
case 文を使用すると、複雑にネストした if 条件文と同じ処理を読みやすい形式で記述できます。case 文の形式は次のとおりです。
case selectorExpression of
caseList1: statement1;
...
caseListn: statementn;
end
selectorExpression は、32 ビット以下の順序型のすべての式(文字列型と 32 ビットを越える順序型は無効)で、各 caseList は以下のいずれかになります。
- コンパイラがプログラムを実行せずに評価できる数値や宣言済みの定数などの式。selectorExpression と互換性のある順序型でなければならない。たとえば、7、True、4 + 5 * 3、'A'、および Integer('A') はすべて caseLists として使用できるが、変数とほとんどの関数呼び出しは使用できない。ただし、Hi や Lo などの一部の組み込み関数は caseList で使用できる。「宣言済みの定数」を参照してください。
- First..Last の形式の部分範囲。First と Last の両方が前項の条件を満たし、First は Last 以下である必要がある。
- item1, ..., itemn の形式のリスト。各 item は前項の条件のいずれかを満たしていることが必要。
caseList が表す値はそれぞれ case 文内で一意でなければならず、部分範囲またはリストが重複してはなりません。case 文の末尾では else 句を使用できます。
case selectorExpression of
caseList1: statement1;
...
caselistn: statementn;
else
statements;
end
statements は、セミコロンで区切られた一連の文です。case 文を実行すると、statement1 ... statementn の 1 つが実行されます。selectorExpression と値の等しい caseList があれば、そのケースリストの文が使用されます。selectorExpression と値の等しい caseList がなく、else 句がある場合は、else 句の文が実行されます。
次の case 文があるとします。
case I of
1..5: Caption := 'Low';
6..9: Caption := 'High';
0, 10..99: Caption := 'Out of range';
else
Caption := '';
end
これは、次のネストした条件文と同じです。
if I in [1..5] then
Caption := 'Low';
else if I in [6..10] then
Caption := 'High';
else if (I = 0) or (I in [10..99]) then
Caption := 'Out of range'
else
Caption := '';
case 文の例をもう 1 つ示します。
case MyColor of
Red: X := 1;
Green: X := 2;
Blue: X = 3;
Yellow, Orange, Black: X := 0;
end;
case Selection of
Done: Form1.Close;
Compute: calculateTotal(UnitCost, Quantity);
else
Beep;
end;
制御ループ
ループを使用すると、一連の文を反復実行し、制御条件または制御変数を使って実行を停止できます。Delphi には 3 種類の制御ループ、repeat 文、while 文、for 文があります。
標準の Break 手続きと Continue 手続きを使用すると、repeat 文、while 文、または for 文の流れを制御できます。Break はそれが含まれている文の実行を終了します。Continue は次の反復に実行を移します。
repeat 文
repeat 文の構文を次に示します。
repeat statement1; ...; statementn; until expression
expression は論理値を返します。until の前の最後のセミコロンはオプションです。repeat 文はそれを構成する一連の文を繰り返し実行し、反復を終了するたびに expression をテストします。expression が True を返すと repeat 文は終了します。expression は 1 回目の実行の後まで評価されないため、一連の文は常に少なくとも 1 回は実行されます。
repeat 文の例を次に示します。
repeat
K := I mod J;
I := J;
J := K;
until J = 0;
repeat
Write('Enter a value (0..9): ');
Readln(I);
until (I >= 0) and (I <= 9);
while 文
while 文は repeat 文に似ていますが、一連の文が 1 回目に実行される前に制御条件が評価される点が異なります。したがって、条件が False であれば、一連の文は一度も実行されません。
while 文の構文を次に示します。
while expression do statement
expression は Boolean 値を返し、statement には複合文を使用できます。while 文はそれを構成する一連の statement を反復実行し、各反復を開始する前に expression をテストします。expression が True を返す限り続行されます。
while 文の例を次に示します。
while Data[I] <> X do I := I + 1;
while I > 0 do
begin
if Odd(I) then Z := Z * X;
I := I div 2;
X := Sqr(X);
end;
while not Eof(InputFile) do
begin
Readln(InputFile, Line);
Process(Line);
end;
for 文
for 文は、repeat 文または while 文とは異なり、ループを実行する回数を明示的に指定する必要があります。for 文の構文を次に示します。
for counter := initialValue to finalValue do statement
または
for counter := initialValue downto finalValue do statement
ここで、
- counter は、for 文が含まれるブロックで宣言される限定子を持たない順序型のローカル変数
- initialValue と finalValue は、カウンタと代入互換の式である
- statement は、カウンタの値を変更しない単純文または構造化文である
for 文は、initialValue の値を counter に代入した後、文を繰り返し実行し、反復のたびに counter をインクリメントまたはデクリメントします。for...to 構文では、counter がインクリメントされ、for...downto 構文ではデクリメントされます。counter が finalValue と同じ値を返すと、statement がもう一度実行された後で、for 文は終了します。つまり、initialValue から finalValue までの範囲のすべての値について 1 回ずつ statement が実行されます。initialValue と finalValue が同じ値である場合、statement は 1 回だけ実行されます。for...to 文で initialValue が finalValue より大きい場合、または for...downto 文で finalValue より小さい場合、statement は一度も実行されません。for 文の終了後(Break または Exit 手続きによって強制終了したのではない場合)、counter の値は未定義になります。
- 警告: 反復変数 counter は、ループ内では変更できません。これには、変数の代入と、手続きの var パラメータへ受け渡しが含まれています。行うと、コンパイル時に警告が発せられます。
ループの実行を制御するために、initialValue と finalValue の各式はループの実行前に一度だけ評価されます。したがって、for...to 文は次の while 構文にきわめて似ていますが、まったく同じではありません。
begin
counter := initialValue;
while counter <= finalValue do
begin
... {statement};
counter := Succ(counter);
end;
end.
この構文と for...to 文の違いは、while ループで各反復の前に finalValue が評価し直される点です。そのため、finalValue が複雑な式である場合は、実行速度が著しく低下する場合があります。また、statement の内部で finalValue の値を変更すると、ループの実行に影響を与える可能性があります。
for 文の例を次に示します。
for I := 2 to 63 do
if Data[I] > Max then
Max := Data[I];
for I := ListBox1.Items.Count - 1 downto 0 do
ListBox1.Items[I] := UpperCase(ListBox1.Items[I]);
for I := 1 to 10 do
for J := 1 to 10 do
begin
X := 0;
for K := 1 to 10 do
X := X + Mat1[I,K] * Mat2[K,J];
Mat[I,J] := X;
end;
for C := Red to Blue do Check(C);
for 文を使用したコンテナの反復処理
Delphi はコンテナに対する for-element-in-collection スタイルの反復をサポートします。次に、コンパイラが認識するコンテナ反復型パターンを示します。
- for Element in ArrayExpr do Stmt;
- for Element in StringExpr do Stmt;
- for Element in SetExpr do Stmt;
- for Element in CollectionExpr do Stmt;
- for Element in Record do Stmt;
反復型変数 Element の型は、コンテナの型と一致する必要があります。ループの各反復で、反復型変数は現在のコレクション メンバを保持します。通常の for ループと同様に、反復型変数は for 文と同じブロックで宣言する必要があります。
- 警告: 反復変数 counter は、ループ内では変更できません。これには、変数の代入と、手続きの var パラメータへ受け渡しが含まれています。行うと、コンパイル時に警告が発せられます。
配列式は、1 次元または多次元、固定長、または動的配列の場合があります。配列は、配列の下限から配列サイズ - 1 まで昇順にスキャンされます。次に、1 次元配列、多次元配列、および動的配列のコード例を示します。
type
TIntArray = array[0..9] of Integer;
TGenericIntArray = array of Integer;
var
IArray1: array[0..9] of Integer = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IArray2: array[1..10] of Integer = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IArray3: array[1..2] of TIntArray = ((11, 12, 13, 14, 15, 16, 17, 18, 19, 20),
(21, 22, 23, 24, 25, 26, 27, 28, 29, 30));
MultiDimTemp: TIntArray;
DynArray: TGenericIntArray;
I: Integer;
begin
for I in IArray1 do
begin
{ Do something with I... }
end;
{ Indexing begins at lower array bound of 1. }
for I in IArray2 do
begin
{ Do something with I... }
end;
{ Iterating a multidimensional array }
for MultiDimTemp in IArray3 do // Indexing from 1..2
for I in MultiDimTemp do // Indexing from 0..9
begin
{ Do something with I... }
end;
{ Iterating over a dynamic array }
DynArray := TGenericIntArray.Create(1, 2, 3, 4);
for I in DynArray do
begin
{ Do something with I... }
end;
end.
次に、文字列式の反復例を示します。
var
C: Char;
S1, S2: String;
Counter: Integer;
OS1, OS2: ShortString;
AC: AnsiChar;
begin
S1 := 'Now is the time for all good men to come to the aid of their country.';
S2 := ''''''';
for C in S1 do
S2 := S2 + C;
if S1 = S2 then
Writeln('SUCCESS #1')
else
Writeln('FAIL #1');
OS1 := 'When in the course of human events it becomes necessary to dissolve...';
OS2 := ''''''';
for AC in OS1 do
OS2 := OS2 + AC;
if OS1 = OS2 then
Writeln('SUCCESS #2')
else
Writeln('FAIL #2');
end.
次に、集合式の反復の例を示します。
type
TMyThing = (one, two, three);
TMySet = set of TMyThing;
TCharSet = set of Char;
var
MySet: TMySet;
MyThing: TMyThing;
CharSet: TCharSet;
C: Char;
begin
MySet := [one, two, three];
for MyThing in MySet do
begin
// Do something with MyThing...
end;
CharSet := [#0..#255];
for C in CharSet do
begin
// Do something with C...
end;
end.
for-in ループ構造を使用するクラスまたはインターフェイスは、指定されたコレクション パターンを実装する必要があります。コレクション パターンを実装する型には次の属性が必要です。
- クラスまたはインターフェイスは、GetEnumerator() というパブリック インスタンス メソッドを含んでいる必要があります。GetEnumerator() メソッドは、クラス、インターフェイス、またはレコード型を返す必要があります。
- GetEnumerator() が返すクラス、インターフェイス、レコードには、MoveNext() というパブリック インスタンス メソッドが含まれている必要があります。MoveNext() メソッドは、Boolean を返す必要があります。for-in ループは、このメソッドをまず呼び出し、コンテナが空でないことを確認します。
- GetEnumerator() が返すクラス、インターフェイス、レコードには Current というパブリック インスタンス読み取り専用プロパティが含まれている必要があります。Current プロパティの型は、コレクションに含まれる型である必要があります。
次に、Delphi の列挙型コンテナの反復型コードを示します。
type
TMyIntArray = array of Integer;
TMyContainerEnumerator = class;
TMyContainer = class
public
Values: TMyIntArray;
function GetEnumerator: TMyContainerEnumerator;
end;
TMyContainerEnumerator = class
Container : TMyContainer;
Index : Integer;
public
constructor Create(AContainer : TMyContainer);
function GetCurrent: Integer;
function MoveNext: Boolean;
property Current: Integer read GetCurrent;
end;
constructor TMyContainerEnumerator.Create(AContainer : TMyContainer);
begin
inherited Create;
Container := AContainer;
Index := - 1;
end;
function TMyContainerEnumerator.MoveNext: Boolean;
begin
Result := Index < High(Container.Values);
if Result then
Inc(Index);
end;
function TMyContainerEnumerator.GetCurrent: Integer;
begin
Result := Container.Values[Index];
end;
function TMyContainer.GetEnumerator: TMyContainerEnumerator;
begin
Result := TMyContainerEnumerator.Create(Self);
end;
var
MyContainer : TMyContainer;
I : Integer;
Counter : Integer;
begin
MyContainer := TMyContainer.Create;
MyContainer.Values := TMyIntArray.Create(100, 200, 300);
Counter := 0;
for I in MyContainer do
Inc(Counter, I);
Writeln('Counter = ', Counter, ' (should be 600)');
ReadLn;
end.
コレクション列挙型の一部として、RAD Studio は、反復処理が完了した際に、列挙子の状態を正式に定義します: ”列挙子の状態は、MoveNext が False を返した後は無効であり、列挙子は解放または再度作成される必要があり、以降アクセスしてはなりません。”
for 文を使用したデータセットの反復処理
Delphi supports for-in syntax construction to iterate over datasets. The compiler recognizes the following dataset iteration pattern:
- for Record in Dataset do Smth;
ここでの Record
は、TDataSet API によってあらわされます。Record は Detaset と同等であると、ここでは考えて構いません。
次のコード スニペットは、Delphi でのデータセットを反復処理します。このサンプル コードでは、Name 列の値を、Memo コントロールに出力する方法について説明しています。
var
ds: TDataSet;
//
FQuery1.SQL.Text := 'SELECT Name FROM Table1';
Memo1.Lines.Clear;
for ds in FDQuery1 do
Memo1.Lines.Add(ds.FieldByName('Name').AsString);
for-in
ループを同時に実行する必要がある場合には、代わりに、TDataSet.View メソッドを使用します(このトピックの後述参照)。 このシナリオでは、for-in
ループにおいて、Record は Dataset と同等ではありません。
次のコード スニペットでは、データセットを列挙するために TDataSet.View メソッドを使用する方法を示しています。
var
ds: TDataSet;
//...
Memo1.Lines.Clear;
for ds in FDQuery1.View(dmAllowClone) do
Memo1.Lines.Add(ds.FieldByName('name').AsString);
サポートされるクラス一覧
次のクラスとその下位クラスでは for-in 構文を使用できます。
- System.Classes.TList
- System.Classes.TCollection
- System.Classes.TStrings
- System.SysUtils.TStringBuilder
- System.Classes.TInterfaceList
- System.Classes.TComponent
- Vcl.Menus.TMenuItem
- Vcl.ActnList.TCustomActionList
- Vcl.ComCtrls.TListItems
- Vcl.ComCtrls.TTreeNodes
- Vcl.ComCtrls.TToolBar
- Data.DB.TFields
- Data.DB.TDataSet
ブロックとスコープ
宣言と文はブロックに整理されます。ブロックはラベルと識別子のローカルな名前空間(スコープ)を定義します。このため、同一の識別子(変数名など)がプログラムの部分によって異なる意味を持つことができます。各ブロックは、プログラム、関数、または手続きの宣言の一部です。プログラム、関数、または手続きの宣言には、対応する 1 つのブロックがあります。
ブロック
ブロックは一連の宣言とその後に続く複合文で構成されます。宣言はすべてブロックの先頭に記述する必要があります。したがって、ブロックは次の形式をとります。
{declarations} begin {statements} end
declarations 部には、変数、定数(リソース文字列を含む)、型、手続き、関数、およびラベルの宣言を任意の順序で記述できます。 プログラム ブロックでは、declarations 部に 1 つまたは複数の exports 句を記述できます(「ライブラリとパッケージ(Delphi)」参照)。
たとえば、次の関数宣言があるとします。
function UpperCase(const S: string): string;
var
Ch: Char;
L: Integer;
Source, Dest: PChar;
begin
...
end;
宣言の 1 行目は関数ヘッダーであり、これに続くすべての行がブロックを構成します。Ch、L、Source、Dest はローカル変数です。これらの変数の宣言は UpperCase 関数のブロックにのみ適用され、program ブロック、またはユニットの interface セクションまたは implementation セクションで同じ識別子が宣言されている場合は、このブロック内でのみそれらの宣言に優先します。
範囲
変数名や関数名などの識別子は、宣言のスコープ内でのみ使用できます。スコープは宣言の場所によって決定されます。プログラム宣言、関数宣言、または手続き宣言で宣言された識別子のスコープは、宣言の行われたブロックに限定されます。ユニットの interface セクションで宣言された識別子のスコープは、宣言の行われたユニットを使用する他のユニットやプログラムを含みます。スコープの狭い識別子、特に関数や手続きで宣言された識別子は、ローカルな識別子と呼ばれます。スコープの広い識別子はグローバルな識別子と呼ばれます。
識別子のスコープを決定する規則を以下に示します。
識別子が宣言された場所 ... | スコープの範囲 ... |
---|---|
プログラム、関数、または手続きの宣言 |
識別子が宣言された場所から現在のブロックの末尾まで。このスコープに含まれるすべてのブロックを含む |
ユニットの interface セクション |
識別子が宣言された場所からユニットの末尾まで、およびそのユニットを使用する他のユニットまたはプログラムまで。 (「プログラムとユニット(Delphi)」を参照してください)。 |
ユニットの implementation セクション。ただし関数または手続きのブロック以外 |
識別子が宣言された場所からユニットの末尾まで。この識別子は、ユニット内のすべての関数または手続きから使用でき、(もしあれば)初期化部および終了処理部からも使用できる |
レコード型の定義。識別子はレコードのフィールドの名前 |
識別子が宣言された場所からレコード型定義の末尾まで。 (「構造型(Delphi)」の「レコード」を参照してください。) |
クラスの定義。識別子はクラスのデータ フィールド プロパティまたはメソッドの名前 |
識別子が宣言された場所からそのクラス型定義の終わりまで、およびそのクラスの派生クラスと、そのクラスとその派生クラスで定義されたすべてのメソッドのブロックまで含む。 (「クラスとオブジェクト(Delphi)」を参照してください)。 |
名前の競合
あるブロックが内部に別のブロックを含んでいる場合、前者は外部ブロック、後者は内部ブロックと呼ばれます。外部ブロックで宣言された識別子が内部ブロックで宣言し直された場合は、宣言した場所から内部ブロックの末尾まで内部の宣言が外部の宣言に先行して、識別子の意味を決定します。たとえば、ユニットの interface セクションで MaxValue という変数を宣言し、同じユニットの関数宣言で同名の別の変数を宣言した場合、関数ブロック内で MaxValue が限定子なしで使用されるときは、2 番目のローカルな宣言が識別子の意味を決定します。同じように、関数の内部で別の関数を宣言すると新しい内部スコープが作成され、この内部スコープでは外側の関数で使用されている識別子をローカルに宣言し直すことができます。
複数のユニットを使用する場合は、スコープの定義がさらに複雑になります。uses 句に書いた各ユニットは、使用される残りのユニットと、uses 句を含む program または unit を包含する新しいスコープを有効にします。uses 句の最初のユニットは最も外側のスコープを表し、それに続く各ユニットは前のユニットが表すスコープの内側の新しいスコープを表します。2 つ以上のユニットの interface セクションで同じ識別子が宣言されている場合、その識別子を限定子なしで参照すると、最も内側のスコープの宣言が選択されます。最も内側のスコープとは、識別子を参照するユニットのスコープか、そのユニットで識別子が宣言されていない場合は uses 句で最後に識別子を宣言したユニットが表すスコープです。
System および SysInit ユニットはすべてのプログラムおよびユニットによって自動的に使用されます。System の宣言は、コンパイラが自動的に認識する定義済みの型、ルーチン、および定数とともに、常に最も外側のスコープを持ちます。
限定識別子(「基本的な構文要素(Delphi)」の「限定識別子」を参照)または with 文(上の「with 文」を参照)を使用できれば、スコープの規則をオーバーライドして、内部宣言をバイパスできます。