制御フロー
メソッドや関数が単純か複雑かに関係なく、開発者は3つのプログラミング構造のうち、1つ以上を常に使用します。 プログラミング構造は、メソッド内でステートメントが実行される順序を決定する実行フローをコントロールします。 3つのタイプの構造があります:
-
シーケンシャル: シーケンシャル構造は単純な線形構造です。 シーケンスとは、4Dが最初から最後まで次々に 実行する一連のステートメントです。 オブジェクトメソッドで頻繁に使用される1行から成るルーチンはもっとも簡単なシーケンシャル構造の例です。 例:
[People]lastName:=Uppercase([People]lastName)
-
分岐: 分岐構造は、条件をテストし、その結果に基づいて異なる流れにメソッドを導きます。 条件は true または false に評価されるブール式です。
If...Else...End if
構文は分岐構造の一例で、処理フローを二つに分岐します。Case of...Else...End case
構文も分岐構造の一つで、処理フローをもっとたくさん分岐することができます。 -
ループ: メソッドの作成にあたって、何度も同じ処理を繰り返すことがあります。 これに実現するために、4D は以下のループ構造を備えています:
ループを制御する方法には、条件が満たされるまでループする方法と、指定した回数だけループする方法の2通りがあります。 各ループ構造はいずれの方法にも用いることができますが、While
ループと Repeat
ループは条件が満たされるまで繰り返す場合に、For
ループは指定した回数だけループする場合の利用に適切です。 For each...End for each
ループは両方を組み合わせることが可能で、オブジェクトやコレクション内でループするために設計されています。
注: 4Dはプログラム構造 (If/While/For/Caes of/Repeat/For each) を512レベルまで入れ子で記述できます。
If...Else...End if
If...Else...End if
による制御フロー構造の正式な構文は以下のようになります:
If(Boolean_Expression)
statement(s)
Else
statement(s)
End if
Else
部分はオプションであり、省略して以下のように記述できます:
If(Boolean_Expression)
statement(s)
End if
If...Else...End if
構造は、条件 (ブール式) が true か false かによって、処理の選択肢を2つメソッドに与えます。 ブール式が true の場合は、テストのすぐ後のステートメントを実行し、 ブール式が FALSE の場合には、Else 文のすぐ後のステートメントを実行します。 任意の Else
が省略されていた場合、End if
のすぐ後のステートメント (あれば) へと実行が続行されます。
ブール式は常に全体が評価されるという点に注意してください。 たとえば、以下のような場合:
If(MethodA & MethodB)
...
End if
この場合、両方のメソッドが true である場合に限り、式は true になります。 しかしながら MethodA が false であっても、4D は MethodB も評価するため、これは時間の無駄になります。 この場合には、以下のような構造を使用するほうが賢明といえます:
If(MethodA)
If(MethodB)
...
End if
End if
上記の結果はほぼ同じで、MethodB は必要な場合にのみ評価されます。
例題
// ユーザーに名前の入力を求めます
$Find:=Request("名前を入力してください")
If(OK=1)
QUERY([People];[People]LastName=$Find)
Else
ALERT("名前が入力されませんでした")
End if
Tip: 一方の条件に実行ステートメントがない分岐処理を書くこともできます。 下のようなコードはどちらも有効です:
If(Boolean_Expression)
Else
statement(s)
End if
または:
If(Boolean_Expression)
statement(s)
Else
End if
Case of...Else...End case
Case of...Else...End case
による制御フロー構造の正式な構文は以下のようになります:
Case of
:(Boolean_Expression)
statement(s)
:(Boolean_Expression)
statement(s)
.
.
.
:(Boolean_Expression)
statement(s)
Else
statement(s)
End case
Else
部分はオプションであり、省略して以下のように記述できます:
Case of
:(Boolean_Expression)
statement(s)
:(Boolean_Expression)
statement(s)
.
.
.
:(Boolean_Expression)
statement(s)
End case
If...Else...End if
と同様に、Case of...Else...End case
構造も処理の選択肢をメソッドに与えます。 If...Else...End
との違いは、Case of...Else...End case
構造が複数のブール式を評価し、その中から最初に true となるステートメントを実行することです。
ブール式の前にはそれぞれコロン (:
) を付けます。 コロンとブ ール式の組み合わせをケースと呼びます。 例えば以下の行はケースです:
:(bValidate=1)
最初に true になったケースに続く (次のケースまでの) ステートメントだけが実行されます。 true になるケースがない場合、どのステートメントも実行されません (Else
文が指定されていない場合) 。
最後のケースの後に Else 文を含むことができます。 すべてのケースが FALSE の場合に、Else
文の後のステートメントが実行されます。
例題
下記の例は数値変数を判定し、対応する数字をアラートボックスに表示します:
Case of
:(vResult=1) // 数値が1の場合
ALERT("一です。") // 1のアラートボックスを表示します
:(vResult=2) // 数値が2の場合
ALERT("二です。") // 2のアラートボックスを表示します
:(vResult=3) // 数値が3の場合
ALERT("三です。") // 3のアラートボックスを表示します
Else // 数値が1,2,3のいずれでもない場合
ALERT("一、二、三のいずれでもありません。")
End case
比較するために、同じことを If...Else...End if
構文で記述すると以下のようになります。
If(vResult=1) // 数値が1の場合
ALERT("一です。") // 1のアラートボックスを表示します
Else
If(vResult=2) // 数値が2の場合
ALERT("二です。") // 2のアラートボックスを表示します
Else
If(vResult=3) // 数値が3の場合
ALERT("三です。") // 3のアラートボックスを表示します
Else // 数値が1,2,3のいずれでもない場合
ALERT("一、二、三のいずれでもありません。")
End if
End if
End if
Case of...Else...End case
構造は、最初に true になったケースだけを実行します。 2つ以上のケースが true の場合は、最初に true になったケースのステートメントだけを実行します。
したがって、階層的なテストを実行するときには、階層上で低い位置にある条件がテスト順序で先に記述されていることを確認する必要があります。 以下の例では、ケース2が true の場合、ケース1も必ず true であるため、ケース1は後に位置すべきです。 このままの順序では、ケース2のステートメントはけっして実行されません:
Case of
:(vResult=1)
... // ステートメントなど
:((vResult=1) & (vCondition#2)) // このケースが判定されることはありません
... // ステートメントなど
End case
vResult = 1の判定により他の条件を見る前に分岐するので、第2のケースが判定されることはありません。 コードが正しく実行されるためには次のように書きます:
Case of
:((vResult=1) & (vCondition#2)) // このケースが先に判定されます
... // ステートメントなど
:(vResult=1)
... // ステートメントなど
End case
さらに階層的なテストを実行したい場合、コードも階層化する必要があります。
Tip: 分岐構造において、ケースに続くステートメントの記述は必須ではありません。 下のようなコードはどちらも有効です:
Case of
:(Boolean_Expression)
:(Boolean_Expression)
...
:(Boolean_Expression)
statement(s)
Else
statement(s)
End case
または:
Case of
:(Boolean_Expression)
:(Boolean_Expression)
statement(s)
...
:(Boolean_Expression)
statement(s)
Else
End case
または:
Case of
Else
statement(s)
End case
While...End while
While...End while
による制御フロー構造の正式な構文は以下のようになります:
While(Boolean_Expression)
statement(s)
{break}
{continue}
End while
While...End while
ループは、ブール式が true である限り、ループ内のステートメントを実行し続けます。 ループの始めにブール式を評価し、ブール式が FALSE の場合にはループをおこないません。
break
および continue
ステートメントについては 後述します。
一般に、While...End while
ループに入る手前で、ブール式で判定する値を初期化しておきます。 通常はブール式が true になるように設定してからループに入ります。
ブール式は、ループ内の要素を使って設定されなければなりません。そうでなければ、ループは永久に続くでしょう。 以下の例では、NeverStop がいつも true であるので、ループは永久に続きます。
NeverStop:=True
While(NeverStop)
End while
このようにメソッドの実行が制御不能になった場合には、トレース機能を使用し、ループを止めて、問題点を追跡することができます。 メソッドのトレース方法については、エラー処理 の章を見てください。
例題
CONFIRM("Add a new record?") // ユーザーに確認します
While(OK=1) // 利用者が望む限りループします
ADD RECORD([aTable]) // 新規にレコードを追加します
End while // ループは必ず End while によって終わります
この例では、まずループに入る前に CONFIRM
コマンドによりシステム変数 OK
がセットされます。 ユーザーがダイアログボックスで OK ボタンをクリックすると、システム変数 OK
に1がセットされ、ループを開始します。 それ以外の場合はシステム変数 OK
に0が設定され、ループをスキップします。 ループに入ると、ADD RECORD
コマンドはループを続けます。これは、ユーザーがレコードを保存した時点で、システム変数 OK
に1が設定されるからです。 ユーザーが最後のレコードを取り消した (保存しない) 時点で、システム変数 OK
に0がセットされ、ループは終了します。
Repeat...Until
Repeat...Until
による制御フロー構造の正式な構文は以下のようになります:
Repeat
statement(s)
{break}
{continue}
Until(Boolean_Expression)
Repeat...Until
ループは、While...End while ループと似ていますが、まずループの後でブール式を判定する点が異なります。 つまり、Repeat...Until
ループは最低でも1回は必ずループを実行しますが、While...End while
ループは最初のブール式が FALSE である場合には、ループを1回も実行しません。
もう一つの While...End while
ループとの相違点は、 Repeat...Until
はブール式が true になるまでループを続行することです。
break
および continue
ステートメントについては 後述します。
例題
以下の例を、While...End while
ループの例と比較してください。 ブール式を、初期化しておく必要がない点に注目してください。システム変数 OK
を初期化する CONFIRM
コマンドはありません。
Repeat
ADD RECORD([aTable])
Until(OK=0)
For...End for
For...End for
による制御フロー構造の正式な構文は以下のようになります:
For(Counter_Variable;Start_Expression;End_Expression{;Increment_Expression})
statement(s)
{break}
{continue}
End for
For...End for
ループは、カウンター変数によりループを制御します:
- カウンター変数 Counter_Variable は、数値変数 (実数または倍長整数) で、Start_Expression に指定した値に初期化されます。
- ループを実行するたびに、任意の引数である Increment_Expression の値がカウンター変数に加算されます。 Increment_Expression を指定しない場合、増分値は1になります。
- カウンターが End_Expression の値を超えた時点で、ループを停止します。
重要: Start_Expression、End_Expression、Increment_Expression の値は、ループの始めに一度だけ評価されます。 これらの数値が変数で指定されている場合、ループ内でその変数の値を変更してもループは影響を受けません。
Tip: 特別な目的のために、カウンター変数 Counter_Variable の値を変更することができます。ループ内でカウンター変数を変更すると、ループはその影響を受けます。
- 通常、Start_Expression は End_Expression より小さい。
- Start_Expression と End_Expression が等しい場合、1回だけループがおこなわれます。
- Start_Expression が End_Expression より大きい場合、Increment_Expression に負の値を指定しない限り、ループはおこなわれません。 次に例を示します。
break
および continue
ステートメントについては 後述します。
基本的な使用例
- 以下の例は、100回の繰り返しをおこないます:
For(vCounter;1;100)
// なんらかの処理
End for
- 以下の例は、配列 anArray の全要素に対して処理をおこないます:
For($vlElem;1;Size of array(anArray))
// 各配列要素に対する処理
anArray{$vlElem}:=...
End for
- テキスト変数 vtSomeText の文字を一つ一つループ処理します:
For($vlChar;1;Length(vtSomeText))
// 文字がタブであれば
If(Character code(vtSomeText[[$vlChar]])=Tab)
// なんらかの処理をします
End if
End for
- 以下の例は、テーブル [aTable] のカレントセクションの各レコードについて処理をおこないます:
FIRST RECORD([aTable])
For($vlRecord;1;Records in selection([aTable]))
// 各レコードに対する処理
SEND RECORD([aTable])
//...
// 次レコードへ移動します
NEXT RECORD([aTable])
End for
プロジェクトで作成する大部分の For...End for
ループは、上記例題のいずれかの形式になるでしょう。
カウンター変数
カウンター変数の減少
ループに際してカウンター変数を増加させるのではなく、減少させたい場合があります。 その場合、Start_Expression に End_Expression より大きい値を設定し、Increment_Expression に負の数を指定する必要があります。 次に挙げる例題は、前述の例と同じ処理を逆 の順序でおこないます:
- 以下の例は、100回の繰り返しをおこないます:
For(vCounter;100;1;-1)
// なんらかの処理
End for
- 以下の例は、配列 anArray の全要素に対して処理をおこないます:
For($vlElem;Size of array(anArray);1;-1)
// 各配列要素に対する処理
anArray{$vlElem}:=...
End for
- テキスト変数 vtSomeText の文字を一つ一つループ処理します:
For($vlChar;Length(vtSomeText);1;-1)
// 文字がタブであれば
If(Character code(vtSomeText[[$vlChar]])=Tab)
// なんらかの処理をします
End if
End for
- 以下の例は、テーブル [aTable] のカレントセクションの各レコードについて処理をおこないます:
LAST RECORD([aTable])
For($vlRecord;Records in selection([aTable]);1;-1)
// 各レコードに対する処理
SEND RECORD([aTable])
//...
// 前レコードへ移動します
PREVIOUS RECORD([aTable])
End for
1より大きな値によるカウンター変数の増加
必要に応じて、Increment_Expression (正または負の値) に、その絶対値が1より大きな値を指定できます。
- 以下の例は、配列 anArray の偶数要素について処理を行います:
For($vlElem;2;Size of array(anArray);2)
// 偶数要素 #2,#4...#2n に対する処理
anArray{$vlElem}:=...
End for
For...End for ループの最適化
カウンター変数 (インタープロセス、プロセス、ローカル変数) には実数、または整数タイプを使用します。 数多く繰り返されるループの場合、とくにコンパイルモードでは、倍長整数タイプのローカル変数を使用してください。
- 次に例を示します:
var $vlCounter : Integer // 整数型のローカル変数を使用します
For($vlCounter;1;10000)
//Do something
End for
ループ構造の比較
For...End for
ループの例をもう一度見てみましょう。 以下の例は、100回の繰り返しをおこないます:
For(vCounter;1;100)
// なんらかの処理
End for
While...End while
ループと Repeat...Until
ループで、同じ処理を実行する方法を調べてみましょう。 以下は、同じ処理を実行する While...End while
ループです:
$i:=1 // カウンターの初期化
While($i<=100) // 100回のループ
// なんらかの処理
$i:=$i+1 // カウンターの増分が必要
End while
同じことを Repeat...Until
ループで記述すると以下のようになります:
$i:=1 // カウンターの初期化
Repeat
// なんらかの処理
$i:=$i+1 // カウンターの増分が必要
Until($i=100) // 100回のループ
For...End for
ループは、While...End while
や Repeat...Until
ループよりも高速です。これは 4D が内部的にカウンター変数のテストおよび増加をおこなうからです。 したがって、可能な限り For...End for
ループの使用が推奨されます。
For...End for の入れ子構造
制御構造は、必要に応じて入れ子にする (ネストする) ことができます。 For...End for
ループも同じです。 誤りを避けるため、各ループ構造ごとに別のカウンター変数を使用してください。
次に例を示します:
- 以下の例は二次元配列の全要素への処理です:
For($vlElem;1;Size of array(anArray))
//...
// 各行に対する処理
//...
For($vlSubElem;1;Size of array(anArray{$vlElem}))
// 各要素に対する処理
anArray{$vlElem}{$vlSubElem}:=...
End for
End for
- 以下の例は、データベースのすべての日付フィールドに対するポインターの配列を作成します:
ARRAY POINTER($apDateFields;0)
$vlElem:=0
For($vlTable;1;Get last table number)
If(Is table number valid($vlTable))
For($vlField;1;Get last field number($vlTable))
If(Is field number valid($vlTable;$vlField))
$vpField:=Field($vlTable;$vlField)
If(Type($vpField->)=Is date)
$vlElem:=$vlElem+1
INSERT IN ARRAY($apDateFields;$vlElem)
$apDateFields{$vlElem}:=$vpField
End if
End if
End for
End if
End for
For each...End for each
For each...End for each
による制御フロー構造の正式な構文は以下のようになります:
For each(Current_Item;Expression{;begin{;end}}){Until|While}(Boolean_Expression)}
statement(s)
{break}
{continue}
End for each