LoginSignup
6
4

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

Last updated at Posted at 2019-03-11

4. 動作の概念

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

4.1. 代入文 (Assignment Statement) と式 (Expressions)

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

代入文 = 
  変数 ":=" 式

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

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

A := 5;
I := I + 1;

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

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

関係演算子 (=, <>, <=, <, >, >=, in) は論理型の結果を返します。

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

  (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] が参照される事はありません。

See also:

4.2. 手続き呼び出し文 (Procedure Statement)

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

例えば標準入力のための 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 discard(v: Integer);
  begin
    
  end; { discard }
begin
  v := 3;
  v2 := test;  (* OK *)
  discard(test); (* OK *)
  Writeln(v);
end. 

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

See also:

4.3. 複合文 (Compound Statement) と空文 (Empty Statement)

複合文 (複文) は複数の単純文を 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

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

Pascal のプログラムが . (ピリオド) で終わっている事でお解りだとは思いますが、; (セミコロン) も英文 (欧文) と同様の使われ方をしています。

See also:

4.4. 繰り返し文 (Repetitive Statements)

ループ文です。

4.4.1. while 文 (While statement)

評価式を判定してから文を実行します。評価式が 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 文 (Repeat statement )

先に文を実行してから条件を判定します。評価式が 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 文 (For statement)

カウンタ用の制御変数 (Control Variable) が初期値~終了値の分だけ繰り返します。"初期値 = 終了値" の場合でも 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

また、制御変数には任意の順序型が使えるため、次のようなコードを書く事も可能です。

Alphabet.pas
program Alphabet(output);
var
  c: Char;
begin
  for c := 'A' to 'Z' do
    writeln(c); 
end.

結果は次の通りです。

A
B
C
︙
Z

任意の順序型が使えるという利点を優先したためか、Pascal の for 文にはステップ構文もありません。制御変数はループの度に次の値 (to) か前の値 (downto) になります。

See also:

(4.4.4.) for in do 文

Delphi (2005 以降) だと for in do 構文で繰り返しコンテナによる反復処理を行えます。

次のパターンで反復処理が可能です。

for 要素型 in 配列式 do ;
for 文字型 in 文字列式 do ;
for 集合の基底型 in 集合式 do ;
for 反復型変数 in コレクション式 do ;

データセットを反復処理させる事もできますが、特殊な例なので省きます。

まだ説明されていない型を使っているので、詳細な説明はせずにコード例だけの紹介とします。

See also:

(4.4.4.1.) 配列式を使った反復処理

const
  i_arr: array [0..2] of Integer = (9, 8, 7);
  c_arr: array [0..2] of Char = ('X', 'Y', 'Z');
var
  i: Integer;
  c: Char;
begin
  for i in i_arr do
    Writeln(i);
  for c in c_arr do
    Writeln(c);
end.

See also:

(4.4.4.2.) 文字列式を使った反復処理

var
  s: string;
  c: Char;
begin
  s := 'Hello,world.';
  for c in s do
    Writeln(c);
end.

See also:

(4.4.4.3.) 集合式を使った反復処理

var
  i: Integer;
begin
  for i in [1, 5, 10, 50, 100, 100] do
    Writeln(i);
  Readln;
end.

実行結果は次のようになります。

1
5
10
50
100

Delphi XE7 以降だと、上記コード中の [1, 5, 10, 50, 100, 100]配列定数式 と認識されるため、実行結果は次のようになります。

1
5
10
50
100
100

Delphi XE7 以降の for in do 構文で反復処理に集合型を使いたい場合には、集合を ( ) で括ります。

var
  i: Integer;
begin
  for i in ([1, 5, 10, 50, 100, 100]) do
    Writeln(i);
  Readln;
end.

上記コードは Delphi XE7 よりも前の Delphi でも問題なく実行できますので、コンパイラバージョンを問わない書き方をしたいのであれば、集合は ( ) で括っておくといいかと思います。

See also:

(4.4.4.4.) コレクション式を使った反復処理

反復用コレクションは少々複雑です。別記事にサンプルがありますので、そちらを参照してください。

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.

See also:

4.5. 条件式 (Conditional Statements)

4.5.1. if 文 (If statement )

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 statement)

セレクタです。

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;

次の図の緑の矩形はすべて文です。
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;

Delphi の case 文の評価式は 32 ビット以下の順序型 である必要があります。この制限のため、次のようなコードは 64bit アプリケーションにおいて問題が発生する可能性があります。

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
  ...

  case Msg.wParam of
    (処理)
  end;

  ...
end;

Msg.wparam は NativeUInt 型であり、32bit 環境では UInt32 (Cardinal) ですが、64bit 環境では UInt64 だからです。この問題の簡単な解決方法は、処理を if 文で書き換える事です。

See also:

4.6. with 文 (With Statement)

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

See also:

4.7. goto 文 (Goto Statement)

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

標準 Pascal で使えるラベルは数値に限定されますが、計算型 goto (computed goto) ではないので、ジャンプ先の指定に変数が使えたりはしません。

See also:

(4.7.1.) Pascal と goto

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

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

See also:

(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. 列挙型と部分範囲型 ] :sushi:

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4