Delphi
Pascal
embarcadero
objectpascal

<4> 動作の概念 (標準 Pascal 範囲内での Delphi 入門)


4. 動作の概念

とは動作を記述するもので、単純文構造文があります。


4.1. 代入文と式

代入文単純文 (単文)です。単純文とは簡単に言えば 文の中で別の文を実行しない単一の文 です。Pascal での代入文は次のようになります。

代入文 = 

変数 ":=" 式

関係演算子の '=' と混同しないように代入記号は := になっています。「何故 := なのか?」と言われても「数学記号がそうだから」としか言えません。

式とは、値を返す構文の事です。

A := 5;

I := I + 1;

標準 Pascal での式の定義は次のようになります。

式 =

単純式 [ "=" | "<>" | "<" | "<=" | ">" | ">=" | "in" 単純式 ].
単純式 =
[ ("+" | "-") ] 項 { ("+" | "-" | "or") 項 }.
項 =
因子 { ("*" | "/" | "div" | "mod" | "and") 因子 }.
因子 =
符号なし定数 | 限界名 | 変数 | 集合構成子 | 関数呼び出し | "not" 因子 | "(" 式 ")".
符号なし定数 =
符号なし数 | 文字列 | 定数名 | "nil".
集合構成子 =
"[" [要素記述 { "," 要素記述 } ] "]".
要素記述 =
順序式 [ ".." 順序式 ].
関数呼び出し =
関数名 [実パラメータ並び].

式にはいろいろあるので詳しい説明は省きますが、一点だけ。

  (X > 0) and (X < 10)

このような論理式で X=0 の場合、(X < 10) を評価する必要はないのですが、これを評価するかどうかは処理系依存となります。式全体の結果が既に確定している場合でもすべて評価するのが完全論理評価、式を左から順に評価して式の結果が確定した時点で評価を打ち切るのが短絡論理評価 (ショートサーキット論理評価)です。

完全論理評価は時として困った問題を引き起こします。例えば A[] という配列が 1 から 10 の要素を持っていた場合、

  I := 0;

repeat
I := I + 1;
until (I > 10) or (A[I] = 0)

I が 11 になった時にも (A[I] = 0) が評価され、A[11] を参照してしまいます。Delphi ではブール短絡評価 {$B+} 指令により、デフォルトで短絡論理評価となっていますので、A[11] が参照される事はありません。

なお、拡張 Pascal では、このあいまいな論理評価を避けるために and_thenor_else という論理演算子を新たに追加しています。これらの演算子は短絡論理評価する事が保証されています。

See also:


4.2. 手続き呼び出し文

手続き呼び出し文も単純文です。

例えば標準入力のための Read(), Readln()、標準出力のための Write(), Writeln() は手続きですが、これを記述した文は手続き呼び出し文です。

program Hello(output);

begin
Writeln('Hello, world.');
end;

手続き
説明

Read()
ファイルからデータを読み込む

Readln()
ファイルからテキストを 1 行読み込む

Write()
ファイルにデータを書き込む

Writeln()
ファイルにテキストを 1 行書き出す

これらの手続きはファイル変数が指定されていない場合、Read(), Readln() は標準入力からデータを読み込み、Write(), Writeln() は標準出力へデータを出力します。

標準 Pascal の場合、結果が不要でも結果を利用しない関数は呼び出せません。

program functiontest(output); 

var
v: Integer;
function test: Integer;
begin
test := 10;
v := 5;
end; { test }
begin
v := 3;
test; (* 結果を利用しない関数は呼び出せない *)
Writeln(v);
end.

この問題を回避するには適当な変数や手続きを用意して読み捨てるのが簡単です。

program functiontest(output); 

var
v: Integer;
v2: Integer;
function test: Integer;
begin
test := 10;
v := 5;
end; { test }
procedure dummy(v: Integer);
begin

end; { dummy }
begin
v := 3;
v2 := test; (* OK *)
dummy(test); (* OK *)
Writeln(v);
end.

Delphi では拡張構文 {$X} がデフォルトで有効 ({$X+}) なので、関数の呼び出しを手続きの呼び出しとして使用できます。

See also:


4.3. 複合文と空文

複合文 (複文)は複数の単純文を beginend で括ったものです。

複合文 = 

begin 文 {";" 文} end

複合文は次のようなコードになります。

begin

a := 1;
b := 2;
c := 3
end

Pascal におけるセミコロン ; は文と文を区切るセパレーターです。C 言語におけるセミコロンは文末のターミネーターです。Pascal によく似ている Ada もセミコロンはターミネーターです。

上記の複合文の改行をなくしてみます。

begin a := 1; b := 2; c := 3 end

何故最後の c := 3 の行にセミコロンが不要なのかなんとなく解ったでしょうか?Pascal では次の複合文もエラーにはなりません。

begin

a := 1;
b := 2;
c := 3;
end

これは空文があると解釈できます。

begin a := 1; b := 2; c := 3; (* ここに空文がある *) end

つまり空の単純文がある訳です。


4.4. 繰り返し文


4.4.1. while 文

評価式を判定してから文を実行します。評価式が True の間繰り返します。

while 文 = 

while 評価式 do 文

先に評価式がきます。単純文の実行はこうなります。

  a := 0;

while a < 10 do
a := a + 1

複数の文を実行するには、複合文を使います。

  a := 0;

b := 0;
while a < 10 do
begin
a := a + 1;
b := b + 2
end

See also:


4.4.2. repeat 文

先に文を実行してから条件を判定します。評価式が True になるまで繰り返します。文はセミコロン区切りです。

repeat 文 = 

repeat 文 {";" 文} until 評価式

次のコードは変数 a が 9 以上になるまで繰り返されます。

  a := 0;

b := 0;
repeat
a := a + 1;
b := b + 2
until a > 9

See also:


4.4.3. for to do 文

カウンタ用の制御変数が初期値~終了値の分だけ繰り返します。"初期値 = 終了値" の場合でも 1 回は繰り返すという事です。

for 文 = 

for 制御変数 ":=" 初期値 to 終了値 do 文

単純文の場合は、

  a := 0;

for i := 0 to 9 do
a := a + i

複数の文を実行するには、複合文を使います。

  a := 0;

b := 0;
for i := 0 to 9 do
begin
a := a + i;
b := b * i
end

Pascal の for 構文は融通が利かず、初期値 <= 終了値 である必要があります。よって次のコードは正しく実行されません。

  { 正しく実行されない例 }

a := 0;
for i := 10 to 1 do (* 終了値よりも開始値が大きい *)
a := a + i

繰り返しをカウントダウンで使いたい場合には for downto do 構文を使います。

for 文 = 

for 制御変数 ":=" 初期値 downto 終了値 do 文

  { 正しく実行される例 }

a := 0;
for i := 10 downto 1 do (* downto を使う *)
a := a + i

See also:


(4.4.4.) for in do 文

Delphi (2005 以降) だと for in do 構文でコンテナの反復処理を行えます。

for in do 文 = 

for 反復型変数 in 繰り返しコンテナ do 文

この構文は拡張 Pascal にもありました。

program ForInDo(output);

var
i: Integer;
begin
for i in [100, 200, 100] do
Writeln(i);
end.

See also:


(4.4.5.) break と continue

標準 Pascal にはループを制御するための Break() 手続きと Continue() 手続きが存在しません。

これらの手続きは繰り返し文の中で作用します。Break() はループを抜ける手続きです。次のコードは 1 から 10 までの値を表示します。

program LoopBreak(output);

{$APPTYPE Console}
var
i: Integer;
begin
i := 0;
while True do
begin
i := i + 1;
writeln(i);
if i >= 10 then
break;
end;
end.

Continue() は次のループに移動します。次のコードは 1 から 100 までの偶数を表示します。

program LoopContinue(output);

{$APPTYPE Console}
var
i: Integer;
begin
for i:=1 to 100 do
begin
if Odd(i) then
Continue;
writeln(i);
end;
end.

この拡張は Turbo Pascal 7.0 以降のようです。MPW Pascal にはそれ以前に leavecycle が存在したようです。

C
Apple
Borland

break
leave
Break

continue
cycle
Continue

標準 Pascal でこれらに近い事をやりたいのであれば、制御変数ともう一段のループを使うか goto 文を使います。

See also:


4.5. 条件式


4.5.1. if 文

if 文は評価式が True の時に文を実行します。評価式が False の時には何も実行されないか else に続く文が実行されます。

if 文 = 

if 評価式 then 文 [else 文].

評価式 は論理型です。評価式の結果が True であるなら then に続く文を実行します。

if a=1 then

b := 1

a が 1 の時、 b に 1 を代入しています。C 言語等とは異なり、複数の評価式を組み合わせない限り、評価式を () で括る必要はありません。もちろん括っても構いません。

評価式が False の時にも文を実行したいのなら else を使います。次の例では評価式が True の時に then に続く文が実行され、False の時には else に続く文が実行されます。

if a=1 then 

b := 1
else
b := 2

複合文を用いて次のようにも書けます。

if a=1 then 

begin
b := 1;
c := 1
end
else
begin
b := 2;
c := 2
end

空文が許されるので、さらに次のようにも書けます。

if a=1 then 

begin
b := 1;
c := 1;
end
else
begin
b := 2;
c := 2;
end;

Pascal におけるセミコロンは文と文を区切るセパレーターなので、最後の文の後ろのセミコロンは付けても付けなくても構いません。

次の図の矩形はすべて文です。

image.png

内側から、緑の矩形が単純文 (代入文)、青の矩形が複合文、赤の矩形が if 文です。

image.png

See also:


4.5.2. case 文

セレクタです。

case 文 = 

case 評価式 of 定数 {, 定数} ":" 文 {"," 定数 {, 定数} ":" 文} end.

選択肢定数に一致すると、その文を実行し case 文の終わりに移動します。

  case i of

1: a := 1;
2: a := 2;
3: a := 3 (* <- ここはセミコロンが必須でない *)
end

Delphi だと選択肢定数に一致しなかった時に実行される else も使えます。

case 文 = 

case 評価式 of 選択肢定数 {, 選択肢定数} ":" 文 {"," 選択肢定数 {, 選択肢定数} ":" 文} [else 文 {";" 文}] end.

else 部分にはセミコロンで区切った文が指定できます。

  case i of

1: a := 1;
2: a := 2;
3: a := 3 (* <- ここはセミコロンが必須でない *)
else
a := 0
end

次の書き方もできますが、ちょっと読みづらいかもしれません。

  case i of

1: a := 1;
2: a := 2;
3: a := 3
else
begin
a := 0
end;
end;

この case 文の拡張ですが、 Turbo Pascal には 1.0 から else が、その前身である BLS Pascal には OTHERS が、MPW Pascal には otherwise がありました。

// Turbo Pascal / Delphi

case n of
1: v := 100;
2: v := 200;
else
v := 0;
end;

// BLS Pascal
case n of
1:
v := 100;
2:
v := 200;
OTHERS:
v := 0;
end;

// MPW Pascal
case n of
1: v := 100;
2: v := 200;
otherwise
v := 0;
end;

次の図の緑の矩形はすべて文です。

image.png

赤の矩形は選択肢定数 (または複数の選択肢定数をカンマで区切った ケースリスト) とコロン : からなるケースラベルです。ケースラベルとそれに続く文を囲った青い矩形はケースセレクタです。

image.png

ケースセレクタもセミコロン区切りです。

Delphi の場合、選択肢定数には部分範囲型も使えます。

  case i of

1, 3, 5:
a := 1;
10..20:
a := 2;
50, 75..100:
a := 3;
else
a := 0
end;

さらに選択肢定数には定数式が使えます。

  case i of

1 + 2:
a := 1;
Ord('A'):
a := 2;
else
a := 0
end;

See also:


4.6. with 文

See also:

with 文の詳細については 7.3. 節で。


4.7. goto 文

goto 文は単純文で、プログラムの別の場所へジャンプするのに使います。

goto 文 = 

goto ラベル.

通常は if 文等と組み合わせて使います。


GotoTest.pas

program GotoTest(Output);

label
1, 2, 3, 5, 7, 11, 13, 17, 19, 23;
begin
goto 17;
1:
Write('brown ');
goto 3;
2:
Write('dog ');
goto 23;
3:
Write('fox ');
5:
Write('jumps ');
goto 11;
7:
Write('lazy ');
goto 2;
11:
Write('over ');
goto 19;
13:
Write('quick ');
goto 1;
17:
Write('The ');
goto 13;
19:
Write('the ');
goto 7;
23:
Writeln;
end.

上記コードの実行結果は次の通りです。

The quick brown fox jumps over the lazy dog

See also:


(4.7.1.) Pascal と goto

Linus Torvalds 氏が Linux カーネルで goto 文が使われている事に対する議論の中で


Of course, in stupid languages like Pascal,

where labels cannot be descriptive, goto's can be bad.


と述べています。意訳すると「Pascal のようにラベルに識別子が使えないクソ言語で goto 使っちゃダメだろうけどね」という事なのですが、先述の通り Delphi ではラベルに識別子が使えますし、Turbo Pascal でも使えました。

Linus 氏の発言は 2003 年の事ですが Pascal という大きな主語で語ってはいけませんよね。既に Delphi 誕生から 8 年、Turbo Pascal 誕生からなら 20 年経っていますので、氏の Pascal に対する知識はアップデートされていない事が伺い知れます (アップデートする必要もなかったのでしょうけれど)。

少なくともこの話をもって「Linus 氏が言ってるから Pascal はクソ言語」と言ってはいけません。

image.png

Ed Post 氏の "Real Programmers Don't Use PASCAL (本物のプログラマはPascalを使わない)" にしても、デバッガを使わずにバグを修正するような凄腕の Fortran 使い以外に言われる筋合いはない話です。他の言語を使ってる人がこれを言ってきたら、まず中身を読んでいません。

"Why Pascal is Not My Favorite Programming Language (何故 Pascal はお気に入りのプログラミング言語ではないのか)" にしても、書いてるのは K&R の Brian W. Kernighan 氏ですので、話半分で聞いた方がいいかもしれません。前提が標準 Pascal なら言ってる事は正しいのですが Pascal という大きな主語だと正しくありません。

例えば MPW や MacApp の開発に携わった Dan Allen 氏は次のように述べています。


MPW Pascal overcomes most of Brian Kernighan's objections to Pascal included in his famous memo "Why Pascal Is Not My Favorite Programming Language."


拡張 Pascal の仕様からの反論もあります。


The criticisms in Kernighan's paper have become outdated and mostly irrelevant with the implementation of Extended Pascal. The paper would not have been mentioned at all, except that the criticisms contained within the paper are used by many in the field today against current implementations of Pascal.


よって、今時この話を持ち出す人もまず中身を読んでいません。あるいは標準 Pascal と Delphi 等のモダンな Pascal との違いを説明できないでしょう。

話を元に戻しますが、goto 文は例えば多重ループを抜けるのに重宝します。但し、ブロックの内側から外側へ抜けるジャンプは可能ですが、ブロックの内側へ入るようなジャンプはできません。

加えて、Delphi の goto手続き内 goto (intraprocedural gotos) であり、手続き/関数の外側へジャンプする事はできません。


(4.8.) 文の正確な定義

Pascal のの正確な定義は次の通りです。

文 =

(ラベル { "," ラベル}) (単純文 | 複合文).

これが何を意味しているのかと言うと、Pascal でと書かれている場所には


  • 単純文

  • 複合文

  • ラベル + 単純文

  • ラベル + 複合文

  • 空文

これらのいずれかが記述できるという事です...つまり、文にはラベルを付けることができます。Delphi だと次のような記述も可能です。


StatementTest1.pas

program StatementTest1(Output);

{$APPTYPE Console}
label
test;
var
v: Integer;
begin
v := 10;

if Odd(v) then
goto test;

if v < 5 then
test: v := 100; // Statement with Label

Writeln(v);
end.


case 文的な記述も可能です。C 言語の switch 文をベタ移植する場合には便利かもしれません。


StatementTest2.pas

program StatementTest2(Output);

{$APPTYPE Console}
label
JP1, JP2, JP3;
var
v: Integer;
begin
v := 7;

if Odd(v) then
goto JP1
else
goto JP2;

JP1:
begin
v := 100;
goto JP3;
end;
JP2:
begin
v := 200;
goto JP3;
end;

JP3:

Writeln(v);
end.


StatementTest1.pas のコードにある goto 文の記述 (then へのジャンプ) が "内側のブロックへの goto" とみなされるかどうかは実装依存だと思われます。


索引

[ ← 3. プログラムヘッダと宣言部 ] [ ↑ 目次へ ] [ → 5. 列挙型と部分範囲型 ]