<11> 手続きと関数 (標準 Pascal 範囲内での Delphi 入門)

# 11. 手続きと関数
**手続き**と**関数**はいわゆる**サブルーチン (副プログラム)** です。**手続き (procedure)** は実行時に値を返さないルーチンです。**関数 (function)** は実行時に値を返すルーチンです。

```
手続き・関数 =
  手続き・関数ヘッダ ";" (ブロック | 指令) ";".
手続き・関数ヘッダ =
  (手続きヘッダ | 関数ヘッダ).
```

Delphi だと正確には次のようになっています。

```
手続き・関数 =
  手続き・関数ヘッダ ";" [ブロック ";"].
手続き・関数ヘッダ =
  (手続きヘッダ | 関数ヘッダ) [";" 指令指定].
指令指定 =
  [指令] [ヒント指令].
```

手続き・関数はプログラムブロックの[手続き・関数宣言部](https://qiita.com/ht_deko/items/2d5996cbfc82d734b247#36-%E6%89%8B%E7%B6%9A%E3%81%8D%E9%96%A2%E6%95%B0%E5%AE%A3%E8%A8%80%E9%83%A8)に書かれます。

## 11.1. 手続き (Procedures)
これまでは[標準手続き](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0#%E6%A8%99%E6%BA%96%E6%89%8B%E7%B6%9A%E3%81%8D-required-procedures)を "使って" きましたが、この節では**手続きの作り方**を述べます。

**手続き宣言**はプログラム部分を定義してそれに名前を関連付ける働きがあります。宣言した手続きは[手続き呼び出し文](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4#42-%E6%89%8B%E7%B6%9A%E3%81%8D%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E6%96%87)によって実行されます。手続きの宣言は **program** と同じ形をしていますが、[プログラムヘッダ](https://qiita.com/ht_deko/items/2d5996cbfc82d734b247#31-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%83%98%E3%83%83%E3%83%80)の代わりに**手続きヘッダ**で始まります。

```
手続きヘッダ =
  procedure 識別子 [仮パラメータリスト].
```

手続きの例は次のようになります。

```pascal:ProcedureTest.pas
program ProcedureTest(output);
var
  A: Integer; {*4}
  B: Integer; {*4, *5}
  procedure Test; {*1}     
  var {*2}
    i: Integer; {*3}
    B: Integer; {*3, *5}
  begin                
    B := 0;
    for i:=1 to 10 do
      B := B + i;
    A := A + B;
  end; { Test }
begin
  A := 1;
  B := 2;
  Test; { *6 }
  Writeln(A);
  Writeln(B);
  Test; { *6 }
  Writeln(A);
  Writeln(B);
end.
```

実行結果は次の通りです。

```
56
2
111
2
```

上記のプログラムは簡単なものですが様々な事項が含まれています。

 1. **手続きヘッダ**: <br>パラメータのない手続き **Test()** です。
 2. **ブロック**: <br>手続きは名前の付いたブロックです。プログラムブロックは **ProcedureTest** で、その中に手続きブロック **Test** があります。手続きのブロックにはプログラムブロックと同じように宣言部があります。
 3. **ローカル変数 (局所変数)**: <br>手続き **Test()** の局所的な変数は **i** と **B** で、これらの変数は手続き **Test()** の有効範囲でしかアクセスできません。
 4. **グローバル変数 (大域変数)**: <br>変数 **A** と **B** がメインプログラム (主プログラム) **ProcedureTest** で宣言された大域的な変数です。これらの変数はプログラムの全域で参照可能で、手続き **Test()** の中で **A** に値を代入しています。 
 5. **有効範囲 (スコープ)**: <br>変数 **i** はグローバル変数にもローカル変数にもありますが、それらは同じ変数ではありません。
 6. [手続き呼び出し文](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4#42-%E6%89%8B%E7%B6%9A%E3%81%8D%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E6%96%87): <br>メインプログラムが手続き **Test()** を 2 回実行しています。このようにプログラム部分を手続きにすると、同じ処理を何度も書かずに済みます。
 
[手続き呼び出し文](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4#42-%E6%89%8B%E7%B6%9A%E3%81%8D%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E6%96%87)の構文は次の通りです。

```
手続き呼び出し文 =
  手続き名 (実パラメータリスト | 書き出しパラメータリスト).
```

※ `書き出しパラメータリスト` は **Write()** および **Writeln()** のための特殊な構文です ([12.3. 節](https://qiita.com/ht_deko/items/391d6c1a53c7aaa3416f#123-%E6%89%8B%E7%B6%9A%E3%81%8D-write-%E3%81%A8-writeln))。

**See also:**

 - [手続きと関数（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E9%96%A2%E6%95%B0%EF%BC%88Delphi%EF%BC%89)
 - [手続きと関数の呼び出し（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E9%96%A2%E6%95%B0%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%EF%BC%88Delphi%EF%BC%89)

### 11.1.1. パラメータリスト (パラメータ並び / Parameter list)
先のプログラムは手続き内でグローバル変数を直接読み書きしており、計算の回数も変更できなかったので、パラメータを持つように変更してみました。

```pascal:ProcedureTest2.pas
program ProcedureTest2(output);
var
  A: Integer;
  B: Integer;
  procedure Test(L: Integer; var V: Integer); { *1, *2, *4 }
  var
    i: Integer;
    B: Integer;
  begin
    B := 0;
    for i:=1 to L do
      B := B + i;
    V := V + B;
  end; { Test }
begin
  A := 1;
  B := 2;
  Test(10, A); { *3 }
  Writeln(A);
  Writeln(B);
  Test(20, A); { *3 }
  Writeln(A);
  Writeln(B);
end.
```

1.&nbsp;**手続きヘッダ**: <br>パラメータリストを持つ形式になっています。

2.&nbsp;**仮パラメータ (仮引数 / パラメータ / parameter / formal-parameter)**: <br>**パラメータリスト**には仮パラメータの名前とその型を書きます。手続き **Test()** は仮パラメータとして **L** と **V** を持っています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/95a1aa34-c272-2c8d-4f99-d59d8b9ed853.png)

3.&nbsp;**実パラメータ (実引数 / 引数 / argument / actual-parameter)**: <br>[手続き呼び出し文](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4#42-%E6%89%8B%E7%B6%9A%E3%81%8D%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E6%96%87)には**実パラメータ**のパラメータリストを含んでいます。実パラメータと仮パラメータは同じ並びである必要があります。渡せるパラメータには**値パラメータ**、**変数パラメータ**、[手続きパラメータ]()、[関数パラメータ]() の 4 種類があります。Delphi にはこの他にも **定数パラメータ**、**out パラメータ**等があります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/59def2f3-d4ed-eb35-6267-87d07058e301.png)

4.&nbsp;**値パラメータ (Value parameters) と変数パラメータ (Variable parameters)**: <br> パラメータ **L** は値パラメータであるので、式 (変数を含む) を実パラメータとして渡せます。予約語 **var** が付加されたパラメータ **V** は**変数パラメータ**であり、手続き内から値を変更する事ができます。

仮パラメータリストの定義は次の通りです。各仮パラメータはセミコロン `;` で区切ります。他言語の**関数プロトタイプ宣言**では `,` を使うので、最初は戸惑うかもしれません。

```
仮パラメータリスト =
  "(" 仮パラメータ {";" 仮パラメータ} ")".
仮パラメータ = 
  [var | CONST | OUT] パラメータ.
パラメータ = 
  識別子 {"," 識別子} [':' 型名].

```

同じ型を持つ仮パラメータはカンマで区切って指定する事ができます。例えば次の手続きヘッダは、

```pascal
procedure TEST(a: Integer; b: Integer);
```

このように短く書く事ができます。[変数宣言部](https://qiita.com/ht_deko/items/2d5996cbfc82d734b247#35-%E5%A4%89%E6%95%B0%E5%AE%A3%E8%A8%80%E9%83%A8-variable-declaration-part)と似た記述が可能です。

```pascal
procedure TEST(a, b: Integer);
```

実パラメータリストの定義は次の通りです。各実パラメータはカンマ `,` で区切ります。

```
実パラメータリスト =
  "(" 実パラメータ {"," 実パラメータ} ")".
実パラメータ = 
  { [変数 | 式] }.
```

パラメータのない手続きの呼び出しでは `(` `)` を省略できます [^3]。

```pascal
DoSomething();
DoSomething;
```

パラメータの種類は次の通りです。

| パラメータの種類 | 予約語| 実パラメータ<br>(呼び出し時) | 入力 | 出力 | 
|:---:|:---:|:---:|:---:|:---:|
| 値パラメータ<br>(値渡し or 参照渡し[^1]) |  | 式 | 〇 | × |
| 変数パラメータ<br>(参照渡し) |**var**|  変数 | 〇 | 〇 |
| 定数パラメータ<br>(値渡し or 参照渡し[^1]) |**const**| 式 | 〇 | × |
| 定数パラメータ<br>(参照渡し) |[Ref] **const**|  式 | 〇 | × |
| **out** パラメータ<br>(参照渡し) |**out**|  変数 | × | 〇 |

値パラメータ (予約語での修飾なし) は、手続きまたは関数の呼び出し時に渡す値に初期化されるローカル変数のような働きをします (値パラメータに代入可能です)。

```pascal:ParamTest.pas
program ParamTest(output);
var
  i: Integer;
  procedure Proc(v: Integer);
  begin
    v := v * 2; { ローカル変数のように使える }
    writeln(v);
  end;
begin
  i := 3;
  Writeln(i); { 3 }
  proc(i);    { 6 } 
  Writeln(i); { 3 }
end.
```

Pascal の値パラメータは値渡しですが、Delphi の値パラメータは値渡しとは限りません。

変数パラメータ (**var**) は参照渡し (変数渡し) を強制しますが、定数パラメータ (**const**) は値渡しを強制するものではありません。パラメータへの代入 (出力) ができない事を強制します。定数パラメータに代入しようとするとコンパイルエラーになります。

Delphi XE3 以降では、**const** に **[Ref]** コンパイラ属性を付けて定数パラメータに参照渡しを強制させる事ができます。コンパイラ属性の指定は **const [Ref]**、**[Ref] const** どちらでも構いません。

**out** パラメータはパラメータへの代入 (出力) のみを許可します。渡された時に実パラメータは初期化されてしまうので実パラメータの値を参照 (入力) する事はできません。**out** パラメータは COM (Component Object Model) 以外の用途で使われることはあまりありません。

多くのケースで **out** パラメータは **var** パラメータと同等の動作 (入力が可能) になるため **var** の使用が推奨されています。もちろん COM (あるいは Delphi for .NET) で使用する場合においてはこの限りではありません。

```pascal:ParamTest.pas
program ParamTest;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

var
  lv1, lv2, lv3, lv4, lv5: Integer;

procedure Test(            v1: Integer;
               var         v2: Integer;
               const       v3: Integer;
               [Ref] const v4: Integer;
               out         v5: Integer);
begin
  Writeln(Format('v1: %.8x : %.8x            ', [@lv1, @v1]));
  Writeln(Format('v2: %.8x : %.8x var        ', [@lv2, @v2]));
  Writeln(Format('v3: %.8x : %.8x const      ', [@lv3, @v3]));
  Writeln(Format('v4: %.8x : %.8x [Ref] const', [@lv4, @v4]));
  Writeln(Format('v5: %.8x : %.8x out        ', [@lv5, @v5]));
end;

begin
  Test(lv1, lv2, lv3, lv4, lv5);
end.
```

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/163fca44-9a9f-c6c6-cfa4-b59e61297711.png)

※サンプルコードを簡潔にするため、外部関数 **Format()** を使っています。

:::note
Delphi 10.1 Berlin 以降、すべてのプラットフォームで次のコンパイラ属性を付ける事が可能です。

 - **var** または **out** パラメータに **[Unsafe]** コンパイラ属性を付けて \[Unsafe\] な属性の変数（またはメンバ）を渡す事を強制させる事ができます。
 - **var** または **out** パラメータに **[Weak]** コンパイラ属性を付けて \[Weak\] な属性の変数（またはメンバ）を渡す事を強制させる事ができます。
 - パラメータに **[Volatile]** コンパイラ属性を付けて最適化による破棄を行わないようにする事ができます。

但し、Delphi 10.4 Sydney で ARC が廃止されたため、現在 `[Unsafe]` と `[Weak]` についてはインターフェイス参照のためだけに利用可能です。
:::

**See also:**

 - [パラメータ（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%EF%BC%88Delphi%EF%BC%89)
 - [値渡しと参照渡し (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E5%88%B6%E5%BE%A1#.E5.80.A4.E6.B8.A1.E3.81.97.E3.81.A8.E5.8F.82.E7.85.A7.E6.B8.A1.E3.81.97)

### 11.1.2. 整合配列パラメータ (Conformant array parameters)
配列型のパラメータをとるルーチンを宣言するとき、そのパラメータ宣言でインデックスの型を指定することはできません。次の宣言はエラーになります。

```pascal
procedure DoSomething(A: array [1..10] of Integer); 
```

**type** arr10 = **array** \[1..10\] **of** Integer; のように、あらかじめ配列型を定義しておけば、パラメータに指定することが可能になります。

```pascal
procedure DoSomething(A: arr10); 
```

それとは別の問題として、ここまでに紹介した文法では `任意の長さの配列をパラメータにとるルーチン` を作る事もできません。

上記の問題を解決するための仕組みが**整合配列パラメータ**なのですが、Delphi ではこのパラメータ形式をサポートしていません。標準 Pascal においてもオプションです。

```
整合配列形式 = 
  パック状整合配列形式 | アンパック状整合配列形式.
パック状整合配列形式 =
  packed array "[" 添字型仕様 "]" of 型名. 
アンパック状整合配列形式 =
  array "[" 添字型仕様 { ";" 添字型仕様} "]" of (型名 | 整合配列形式).
添字型仕様 =
  識別子 ".." 識別子 ":" 順序型名
```

次の例は任意の長さの文字列 (文字配列) を受け取り、シーザー暗号で出力します。

```pascal:
program CaesarCipher(output);

type
  Positive = 1..MaxInt;

var
  Str1: packed array [1..3] of Char;
  Str2: packed array [1..12] of Char;
  
procedure CCStr(Str: packed array [M..N: Positive] of Char);
var
  i: Integer;
begin
  for i := M to N do
    Write(Chr(Ord(Str[i]) - 1));
  Writeln;  
end;

begin
  Str1 := 'IBM';
  CCStr(Str1);
  Str2 := 'Hello,World.';
  CCStr(Str2);
end.
```

実行結果は次のようになります ([ideone.com](https://ideone.com/oza8qS) で試せます)。

```
HAL
Gdkkn+Vnqkc-
```

標準 Pascal の規格で定義されている言語機能をすべて対応した上で、この整合配列パラメータに対応すれば **標準 Pascal 水準 1** (Standard Pascal Level 1)、対応しなければ **標準 Pascal 水準 0** (Standard Pascal Level 0) となります。

### 11.1.3. 再帰手続き (Recursive procedures)
ルーチン内でそのルーチン自身を呼び出すことを**再帰呼出し (Recursive call)**と言い、それを行う手続きを**再帰手続き**と呼びます。関数なら**再帰関数**です。

次のコードは階乗を求める再帰手続きです。

```pascal:RecursiveProcedureTest.pas
program RecursiveProcedureTest(output);
var
  f: Integer;
  procedure fact(n: Integer; var v: Integer);
  begin
    if (n > 0) then
      begin
        v := v * n;
        fact(n - 1, v);
      end;  
  end;    
begin
  f := 1;
  fact(5, f);
  Writeln(f);
end.
```

### 11.1.4. 手続きパラメータ (Procedural parameters)
Delphi ではこのパラメータ形式をサポートしていません。また、標準 Pascal では[標準手続き](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0#%E6%A8%99%E6%BA%96%E6%89%8B%E7%B6%9A%E3%81%8D-required-procedures) を実手続きパラメータとして渡せません。

| パラメータの種類 | 予約語|仮パラメータ<br>(宣言時) | 実パラメータ<br>(呼び出し時) |
|:--------------|:---:|:------------------:|:------------------:|
| 手続きパラメータ || 手続きヘッダ | 手続き |

```pascal:ProcParamTest.pas
program ProcParamTest(output);
var
  A: Integer;
  procedure Proc1(V: integer);
  begin
    A := A + (V * 3);
  end; { Proc1 }
  procedure Proc2(V: integer);
  begin
    A := A - (V * 5);
  end; { Proc2 }
  procedure Test(procedure Proc(V: integer));
  begin
    Proc(10);
  end; { Test }
begin
  A := 0;

  Test(Proc1);
  Writeln(A);

  Test(Proc2);
  Writeln(A);
end.
```

実行結果は次の通りです。

```
 30
-20
```

### (11.1.5.) オープン配列パラメータ (Open Array Parameters)
Delphi では、**配列パラメータ**を実現するための別の解として**オープン配列パラメータ**が実装されています。

オープン配列パラメータを使用すると、基底型が同じでサイズ (要素数) だけが異なる配列をルーチンに渡す事ができます。

```pascal
procedure DoSomething(A: array of Integer);
```
オープン配列パラメータの構文は[動的配列](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac#612-%E5%8B%95%E7%9A%84%E9%85%8D%E5%88%97-dynamic-arrays)の構文に似ていますが同じではありません。実パラメータとして[動的配列](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac#612-%E5%8B%95%E7%9A%84%E9%85%8D%E5%88%97-dynamic-arrays)だけでなく[静的配列](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac#613-%E9%9D%99%E7%9A%84%E9%85%8D%E5%88%97%E3%81%A8%E5%8B%95%E7%9A%84%E9%85%8D%E5%88%97%E3%81%AE%E7%95%B0%E3%81%AA%E3%82%8B%E6%8C%AF%E3%82%8B%E8%88%9E%E3%81%84)も渡せます。ルーチンの中では次のようにして渡された配列を処理できます。

```pascal
procedure Clear(var A: array of Real);
var
   I: Integer;
begin
  for I := 0 to High(A) do 
    A[I] := 0;
end;
```

パラメータに配列そのものを渡しているわけではないため、SetLength() で動的配列の要素数を変更したりする事はできず、

```pascal
procedure Clear(var A: array of Real);
var
  I: Integer;
begin
  SetLength(A, 10);        // 変更できない
  for I := 0 to High(A) do
    A[I] := 0;
end;

var
  ra: array of Real;

begin
  SetLength(ra, 5); // 配列の要素数を 5 に
  Clear(ra);
end.
```

二次元のオープン配列パラメータなんていうものもありません。

```pascal:
procedure Clear(var A: array of array of Real); // NG
```

オープン配列パラメータを使ったルーチンには、[オープン配列コンストラクタ](#1117-%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E9%85%8D%E5%88%97%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF-open-array-constructors)も渡すことができます。

**See also:**

 - [System.Slice (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Slice)


### (11.1.6.) 型可変オープン配列パラメータ (Variant Open Array Parameters)
型可変オープン配列パラメータ (型なしの配列パラメータ) を使用すると、基底型すら異なる配列を渡す事ができます。

```pascal
procedure DoSomething(A: array of const);
```

**array of const** は **array of** TVarRec と同じなので、TVarRec.VType を使って型ごとに処理を分岐させる事ができます。

```pascal
function MakeStr(const Args: array of const): string;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to High(Args) do
     with Args[I] do
        case VType of
          vtInteger:        Result := Result + IntToStr(VInteger);
          vtBoolean:        Result := Result + BoolToStr(VBoolean);
          vtChar:           Result := Result + VChar;
          vtExtended:       Result := Result + FloatToStr(VExtended^);
          vtString:         Result := Result + VString^;
          vtPChar:          Result := Result + VPChar;
          vtObject:         Result := Result + VObject.ClassName;
          vtClass:          Result := Result + VClass.ClassName;
          vtAnsiString:     Result := Result + string(VAnsiString);
          vtUnicodeString:  Result := Result + string(VUnicodeString);
          vtCurrency:       Result := Result + CurrToStr(VCurrency^);
          vtVariant:        Result := Result + string(VVariant^);
          vtInt64:          Result := Result + IntToStr(VInt64^);
        end;
end;
```

型可変オープン配列パラメータにも[オープン配列コンストラクタ](#1117-%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E9%85%8D%E5%88%97%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF-open-array-constructors)を渡すことができます。

**See also:**

 - [System.TVarRec（Delphi）(DocWiki)](http://docwiki.embarcadero.com/Libraries/Rio/ja/System.TVarRec)


### (11.1.7.) オープン配列コンストラクタ (Open Array Constructors)
**オープン配列コンストラクタ**により、関数および手続き呼び出し内で、直接、配列を構築することができます。それらは、[オープン配列パラメータ](#1115-%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E9%85%8D%E5%88%97%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF)または、[型可変オープン配列パラメータ](https://qiita.com/ht_deko/items/b93ac03bfee002f17137#1116-%E5%9E%8B%E5%8F%AF%E5%A4%89%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E9%85%8D%E5%88%97%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF)としてのみ渡すことができます。


例えば次のような定義 (オープン配列パラメータ) があった場合、

```pascal
procedure Add(A: array of Integer);
```

オープン配列コンストラクタを使って Add() 手続きを次の文で呼び出すことができます：

```pascal
Add([5, 7, I, I + J]);
```

次のような定義 (型可変オープン配列パラメータ) なら、

```pascal
procedure Add(A: array of const);
```

オープン配列コンストラクタを使って Add() 手続きを次の文で呼び出すことができます：

```pascal
Add([5, 7, I, I + J]);
Add([100, 'A', 3.14]);
```

オープン配列コンストラクタを頻繁に使う最も有名なルーチンは System.SysUtils にある **Format()** 関数です。 

```pascal
  uses
    ..., System.SysUtils;

  s := Format('%s: %.4d', ['Hello', 123]);
```

**See also:**

 - [オープンな配列コンストラクタ (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E9%96%A2%E6%95%B0%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%EF%BC%88Delphi%EF%BC%89#.E3.82.AA.E3.83.BC.E3.83.97.E3.83.B3.E3.81.AA.E9.85.8D.E5.88.97.E3.82.B3.E3.83.B3.E3.82.B9.E3.83.88.E3.83.A9.E3.82.AF.E3.82.BF)
 - [System.SysUtils.Format() (DocWiki)](http://docwiki.embarcadero.com/Libraries/Rio/ja/System.SysUtils.Format)

### (11.1.8.) 型なしパラメータ (Untyped Parameters)

パラメータは次のように定義されていました。

```
パラメータ = 
  識別子 {"," 識別子} [':' 型名].
```

よく見ると解るのですが、パラメータは `:型名` を省略する事ができます。

具体的には値パラメータ以外のパラメータ (**var** / **const** / **out**) であれば、型を省略した **型なしパラメータ** にする事ができます。これにより、`任意の長さの配列をパラメータにとるルーチン` を作る事が可能になります。

例えば `System.FillChar()` の最初のパラメータは型なしパラメータとなっています。

```pascal
procedure FillChar(var X; Count: NativeInt; Value: Integer);
```

:::note
短い文字列 (ShortString) に限れば、型なしパラメータを使わずとも "任意の長さの配列パラメータ問題" を回避する方法があります。後述の [(11.1.9.) オープン文字列パラメータ (Open String Parameters)](#1119-%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E6%96%87%E5%AD%97%E5%88%97%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF-open-string-parameters) を参照してください。
:::

**See also:**

 - [System.FillChar (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.FillChar)

### (11.1.9.) オープン文字列パラメータ (Open String Parameters)
Delphi のオープン文字列パラメータは、**ANSI 版 Delphi** かつ **String** が短い文字列 (ShortString) である (`{$H-}`) 場合にのみ意味を持ちます。

次に示すコードのパラメータ S の **string** はString 型を示しているのではなく、オープン配列パラメータの文字列版である **オープン文字列パラメータ** を示しています。長さの異なる文字列変数をルーチンのパラメータとして渡す事ができます。

```pascal
program OpenStringParameterTest(Output);
{$APPTYPE Console}

var
  S1: string[10];
  S2: string[20];

  procedure AssignStr(var S: string); // Delphi 1 以前ではオープン文字列パラメータ
  begin                               // Delphi 2 以降では長い文字列の変数 (var) パラメータ 
    S := '0123456789ABCDEF';
  end; { AssignStr }

begin
  AssignStr(S1); { '0123456789' }
  AssignStr(S2); { '0123456789ABCDEF' }
end.
```

コンパイラ指令 `{$P}` はパラメータの **string** をオープン文字列パラメータとみなすかどうかのスイッチです。デフォルトで`{$P+}` ですが、`{$P-}` だと **string** パラメータはただの変数パラメータとみなされます。

Delphi 1 では上記コードのコンパイルが通ったのですが、Delphi 2 以降では、コンパイルエラーになりました。S1 と S2 が **短い文字列 (ShortString)** であるのに対し、S はオープン文字列パラメータではなく**長い文字列 (string) の変数パラメータ**だからです。

この互換性の問題を解決するためには 2 つの方法があります。一つはコンパイラ指令 `{$H-}` を使い、**string 型を短い文字列 (ShortString) とみなす**ようにする方法です。影響範囲が広いですが、大量のコードを移植しなければならない時には有効でしょう。

もう一つは **OpenString** 識別子を使う方法です。OpenString は正確には型ではなく、オープン文字列パラメータを示す識別子で、次のコードでは、パラメータ S はオープン文字列パラメータとして認識されます (型としては **ShortString**)。

```pascal
  procedure AssignStr(var S: OpenString);
  begin
    S := '0123456789ABCDEF';
  end; { AssignStr }
```

”String” という識別子を使っていないため、コンパイラ指令 `{$P}` の影響も受けません。

コンパイラ指令 `{$V}` というのもあります。このオプションは本質的に **"安全でないバージョンのオープン文字列パラメータ"** を提供します。`{$P+}` が指定されている場合には、**string** パラメータはオープン文字列パラメータですので、`{$V}` の効果はありません。

```pascal
program OpenStringParameterTest2(Output);
{$APPTYPE Console}

  procedure MyProc(var s: string); { ここの "string" の意味は？}
  begin
    s := 'abcdefghijk'; { 11 文字の代入 }
  end; { MyProc }

var
  ss: string[5]; { 短い文字列 (5 文字分) }
begin
  MyProc(ss);
end.
```

上記コードと `{$P}` および `{$V}` コンパイラ指令の関係は次の通りです。

| {$P} | {$V} | 説明|
|:-:|:-:|:--|
| `{$P+}`  | `{$V+}` or `{$V-}` | オープン文字列パラメータ。<br>S への割り当てが実パラメータで宣言された<br>サイズを超えないことを保証する。 |
| `{$P-}`  | `{$V+}` | MyProc(ss) は型不一致の<br>コンパイルエラーを生成する。|
| `{$P-}`  | `{$V-}` | MyProc はコンパイラエラーを生成しないが、<br>プログラム内で上書きエラーになり、<br>システムがクラッシュする可能性がある。 |

**String** が短い文字列 だったのは Delphi 1 とそれ以前の Turbo Pascal ですから、これらのコンパイラ指令はもはや気にする必要がないと思います。

むしろ **Unicode 版 Delphi** になってずいぶん経ちますが、未だにコンパイラ指令 `{$H}`・`{$P}`・`{$V}` がプロジェクトオプションに存在するのがとても不思議です。

**See also:**

 - [オープン文字列パラメータ（Delphi）](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E6%96%87%E5%AD%97%E5%88%97%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%EF%BC%88Delphi%EF%BC%89)
 - [System.Openstring (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Openstring)
 - [VARSTRINGCHECKS（var 文字列チェック）（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/VARSTRINGCHECKS%EF%BC%88var_%E6%96%87%E5%AD%97%E5%88%97%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%EF%BC%89%EF%BC%88Delphi%EF%BC%89)

### (11.1.10.) デフォルトパラメータ (Default Parameters)
手続きや関数のヘッダー (仮パラメータ) で、**デフォルトパラメータ**値を指定できます。デフォルト値を指定できるのは、値パラメータと定数パラメータだけです。 

たとえば、次のような宣言は

```pascal
procedure FillArray(A: array of Integer; Value: Integer = 0);
```

次のいずれの呼び出しでも結果は同じになります。

```pascal
FillArray(MyArray);
FillArray(MyArray, 0);
```

複数のパラメータを一度に宣言する場合には、デフォルト値は指定できません。 従って、次の宣言は無効です。

```pascal
function MyFunction(X, Y: Real = 3.5): Real;  // syntax error
```

デフォルト値を指定するパラメータは、パラメータ リストの末尾に置かなければなりません。 つまり、あるパラメータにデフォルト値を指定したら、それ以降のパラメータにはすべてデフォルト値を指定する必要があります。 従って、次の宣言は無効です。

```pascal
procedure MyProcedure(I: Integer = 1; S: string);  // syntax error
```

**See also:**

 - [デフォルトパラメータ（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%EF%BC%88Delphi%EF%BC%89#.E3.83.87.E3.83.95.E3.82.A9.E3.83.AB.E3.83.88_.E3.83.91.E3.83.A9.E3.83.A1.E3.83.BC.E3.82.BF)

### (11.1.11.) ルーチンのオーバーロード (Overloading Routines)
同じスコープ内で同じ名前のルーチンを複数宣言することができます。これは、**オーバーロード**と呼ばれます。オーバーロードされるルーチンは、**overload** 指令を付けて宣言する必要があり、パラメータ リストで区別できなければなりません。

```pascal
 function Divide(X, Y: Real): Real; overload;
 begin
   Result := X / Y;
 end

 function Divide(X, Y: Integer): Integer; overload;
 begin
   Result := X div Y;
 end;
```
オーバーロードされたルーチンは、受け取るパラメータの数またはそれらのパラメータの型で区別できなければなりません。

**See also:**

 - [手続きと関数のオーバーロード (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E9%96%A2%E6%95%B0%EF%BC%88Delphi%EF%BC%89#.E6.89.8B.E7.B6.9A.E3.81.8D.E3.81.A8.E9.96.A2.E6.95.B0.E3.81.AE.E3.82.AA.E3.83.BC.E3.83.90.E3.83.BC.E3.83.AD.E3.83.BC.E3.83.89)

### (11.1.12.) Exit() 手続き

Delphi では **Exit() 手続き**を使って現在のルーチンから抜ける事ができます。メインプログラムで実行するとプログラムが終了します。

```pascal
procedure foo(a: Integer);
begin
  if (a < 0) and (a > 100) then
    Exit;
  ...
end;
```

**See also:**

 - [System.Exit (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Exit)

### (11.1.13.) Halt() 手続き

Delphi では **Halt() 手続き**を使ってプログラムを **異常終了** させる事ができます。パラメータとして終了コードを渡す事ができます。正常終了する手順は次の通りです。

 - **VCL アプリケーションの場合:**<br>Application.Terminate() を呼び出します。
 - **FMX アプリケーションの場合:**<br>Application.Terminate() を呼び出します。
 - **コンソールアプリケーションの場合:**<br>メインプログラムで Exit() 手続きを呼び出します。

**See also:**

 - [System.Halt (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Halt)

## 11.2. 関数 (Functions)
**関数宣言**はプログラム部分を定義してそれに名前を関連付ける働きがあります。宣言した関数は**関数呼び出し**によって実行されます。関数宣言は **program** とほぼ同じ形をしていますが、[プログラムヘッダ](https://qiita.com/ht_deko/items/2d5996cbfc82d734b247#31-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%83%98%E3%83%83%E3%83%80)の代わりに**関数ヘッダ**で始まります。

```
関数ヘッダ =
  functon 識別子 [仮パラメータリスト] ":" 型名.
```

ほぼ**手続き**と同じですが、関数は値を返す (**結果** / **戻り値**) ため、関数呼び出しは、代入や演算子内の式として使用できます。

### 結果型 (Result Type)

結果の型 (**結果型**) は[単純型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b)または[ポインタ型](https://qiita.com/ht_deko/items/745ad4a792d277d33831)の識別子である必要があります。標準 Pascal では結果型に[構造化型](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac)は使えません。Delphi だと一旦型を定義すれば結果型に[構造化型](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac)が使えます。

```pascal:ResultTest.pas
program ResultTest;

type
  Str10 = array [1..10] of Char;
  TIDPass =
    record
      ID: Integer;
      Pass: Str10;
    end;

// NG
function Sub1: array [1..10] of Char;
begin
  ...
end;

// OK
function Sub1: Str10;
begin
  ...
end;

// NG
function Sub2: record ID: Integer; Pass: Str10 end;
begin
  ...
end;

// OK
function Sub2: TIDPass;
begin
  ...
end;

begin

end.
```

### 結果 (Result)

Pascal では関数が返す値を **結果 (result)** と呼びます。他の言語では **戻り値 (return value)** と呼ばれる事が多いですが、当記事では由緒正しく`結果`と表記しています。

関数で結果を返すには、標準 Pascal だと関数名に対して値を代入します。関数名は、その関数の結果を保持する特殊な変数として使用できます。

```pascal
function Add(A, B: integer): Integer;
begin
  Add := A + B;
end;
```

Delphi では拡張構文 `{$X}` がデフォルトで有効 (`{$X+}`) になっているので、関数ブロックで定義済みの変数 **Result** を使って、関数の結果を保持することができます。Result 変数のスコープは関数ブロック内です。

```pascal
function Add(A, B: integer): Integer;
begin
  Result := A + B;
end;
```

Result 変数を使うメリットは主に 2 つあります。

 1. 関数名の変更 (リファクタリング) が容易になる。
 2. 結果への代入と再帰関数を混同しなくなる。

Delphi 2009 以降では **Exit()** 手続きで、関数の結果をパラメータで受け取れるようになりました。C 言語の return 文のような記述が可能になっています。

```pascal
function Add(A, B: integer): Integer;
begin
  Exit(A + B);
end;
```

**See also:**

 - [拡張構文（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/Rio/ja/%E6%8B%A1%E5%BC%B5%E6%A7%8B%E6%96%87%EF%BC%88Delphi%EF%BC%89)

### 関数呼び出し (Function Call)
[4.2. 節](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4#42-%E6%89%8B%E7%B6%9A%E3%81%8D%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E6%96%87) で説明した通り、標準 Pascal は結果が不要でも結果を利用しない関数は呼び出せません。つまり、標準 Pascal での関数は必ず式の中で使われるため、**関数呼び出し文**というのはありませんでした。Delphi では結果を破棄できるため、**関数呼び出し文**が成立します。

また、パラメータのない関数の呼び出しでは `(` `)` を省略できます [^3]。

```pascal
v := DoSomething();
v := DoSomething;
```

**See also:**

 - [拡張構文（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%8B%A1%E5%BC%B5%E6%A7%8B%E6%96%87%EF%BC%88Delphi%EF%BC%89)

### 再帰関数 (Recursive functions)

次のコードは階乗を求める再帰関数です。

```pascal:RecursiveFunctionTest.pas
program RecursiveFunctionTest(output);
var
  f: Integer;
  function fact(n: Integer): Integer;
  begin
    if (n = 0) then
      fact := 1
    else
      fact := 
      fact(n - 1) * n;
  end;    
begin
  f := fact(5);
  Writeln(f);
end.
```

関数名が代入文の左辺にある場合、コンパイラはそれを結果を記録するために使われているものと見なします。関数名がステートメントブロックのその他の場所にある場合、コンパイラはそれをその関数の再帰呼び出しと解釈します。

結果を返すのに Result 変数を使うと再帰呼び出しとの判別が容易になります。

```pascal:RecursiveFunctionTest.pas
program RecursiveFunctionTest(output);
var
  f: Integer;
  function fact(n: Integer): Integer;
  begin
    if (n = 0) then
      Result := 1
    else
      Result := 
      fact(n - 1) * n;
  end;    
begin
  f := fact(5);
  Writeln(f);
end.
```


### 11.2.1. 関数パラメータ (Functional parameters)
[手続きパラメータ](#1114-手続きパラメータ-procedural-parameters)と同様、Delphi ではこのパラメータ形式をサポートしていません。標準 Pascal では[標準関数](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0#%E6%A8%99%E6%BA%96%E9%96%A2%E6%95%B0-required-functions) を実関数パラメータとして渡せません。

| パラメータの種類 | 予約語|仮パラメータ<br>(宣言時) | 実パラメータ<br>(呼び出し時) |
|:--------------|:---:|:------------------:|:------------------:|
| 関数パラメータ || 関数ヘッダ | 関数 |

次に挙げるコードは『J&W (初版・第 2 版)』にある関数パラメータの例です。

関数 `zero()` の第 1 パラメータには、`function sin(x: Real): Real;` のような関数が渡される事が想定されていますが、古い Pascal では関数パラメータは結果の型さえ合致していればよく、渡す関数のパラメータを記述する必要はありませんでした。

```pascal:bisect.pas
{ program 11.6
  find zero of a function by bisection }
program bisect(input, output);

const eps = 1E-14;
var x, y: real;

function zero(function f: real; a, b: real): real;
var x, z: real; s: boolean;
begin
  s := f(a) < 0;
  repeat
    x := (a + b) / 2.0;
    z := f(x);
    if (z < 0) = s then a := x else b := x;
  until abs(a - b) < eps;
  zero := x;
end; { zero }

begin
  read(x, y); writeln(x, y, zero(sin, x, y));
  read(x, y); writeln(x, y, zero(cos, x, y));
end. { main }
```

標準 Pascal では関数パラメータを関数ヘッダと同じ書式で書く必要があります。ただし、**Pascal-P5** では次のコードはコンパイル可能ですが実行時エラーが発生します。

```pascal:bisect.pas
{ program 11.6
  find zero of a function by bisection }
program bisect(input, output);

const eps = 1E-14;
var x, y: real;

function zero(function f(v: real): real; a, b: real): real;
var x, z: real; s: boolean;
begin
  s := f(a) < 0;
  repeat
    x := (a + b) / 2.0; z := f(x);
    if (z < 0) = s then a := x else b := x;
  until abs(a - b) < eps;
  zero := x;
end; { zero }

begin
  read(x, y); writeln(x, y, zero(sin, x, y));
  read(x, y); writeln(x, y, zero(cos, x, y));
end. { main }
```

何故実行時エラーが発生するかというと、`sin()` や `cos()` が[標準関数](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0#%E6%A8%99%E6%BA%96%E9%96%A2%E6%95%B0-required-functions)だからです。標準 Pascal では[標準関数](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0#%E6%A8%99%E6%BA%96%E9%96%A2%E6%95%B0-required-functions)を関数パラメータとして渡せなくなっています [^2]。次のように[標準関数](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0#%E6%A8%99%E6%BA%96%E9%96%A2%E6%95%B0-required-functions)をラッピングしてやれば実行時エラーは発生しません。

```pascal:bisect.pas
{ program 11.6
  find zero of a function by bisection }
program bisect(input, output);

const eps = 1E-14;
var x, y: real;

function sin2(v: real): real;
begin
  sin2 := sin(v);
end; { sin2 }

function cos2(v: real): real;
begin
  cos2 := cos(v);
end; { cos2 }

function zero(function f(v: real): real; a, b: real): real;
var x, z: real; s: boolean;
begin
  s := f(a) < 0;
  repeat
    x := (a + b) / 2.0; z := f(x);
    if (z < 0) = s then a := x else b := x;
  until abs(a - b) < eps;
  zero := x;
end; { zero }

begin
  read(x, y); writeln(x, y, zero(sin2, x, y));
  read(x, y); writeln(x, y, zero(cos2, x, y));
end. { main }
```

詳細は後述しますが、Delphi では**手続き型 (Procedural Types)** を用いて次のように書く事ができます。パラメータの型や結果型を厳密に合わせる必要があるため、Real 型を Extended 型に変更しています。Delphi の `sin()` は `function Sin(const X: Extended): Extended;` と定義されています。 

```pascal:bisect.dpr
{ program 11.6
  find zero of a function by bisection }
program bisect(input, output);

type Tf = function (const v: Extended): Extended;
const eps = 1E-14;
var x, y: Extended;

function zero(f: Tf; a, b: Extended): Extended;
var x, z: Extended; s: boolean;
begin
  s := f(a) < 0;
  repeat
    x := (a + b) / 2.0; z := f(x);
    if (z < 0) = s then a := x else b := x;
  until abs(a - b) < eps;
  result := x;
end; { zero }

begin
  read(x, y); writeln(x, y, zero(sin, x, y));
  read(x, y); writeln(x, y, zero(cos, x, y));
end. { main }
```

このプログラムを実行して次のように入力すると、

```
-1 1 1 2〔Enter〕 
```

このような結果が得られます。

```
-1.00000000000000E+0000  1.00000000000000E+0000  -7.10542735760100E-0015
 1.00000000000000E+0000  2.00000000000000E+0000   1.57079632679490E+0000
```

**See also:**

 - [手続き型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E5%9E%8B)
 - [関数ポインタ (Wikipedia)](https://ja.wikipedia.org/wiki/%E9%96%A2%E6%95%B0%E3%81%B8%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF)
 - [コールバック (情報工学) (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF_(%E6%83%85%E5%A0%B1%E5%B7%A5%E5%AD%A6))
 - [総和 (Σ) 関数を書く (Qiita)](https://qiita.com/ht_deko/items/52b76827682e28403f46)

### 11.2.2. 副作用 (Side effect)

『J&W』に掲載されていた、手続きや関数内からグローバル変数を操作すると思わぬ結果をもたらす、というコードのサンプルです。結果を予想するのがとても難しいです。

```pascal:SideEffect.pas
program SideEffect(Output);

   { プログラム 11.10 - 関数の副作用の例 }

  var
    A, Z: Integer;

  function Sneaky(X: Integer): Integer;
  begin
    Z := Z - X { Z への副作用 };
    Sneaky := Sqr(X);
  end; { Sneaky }
  
begin
  Z := 10; A := Sneaky(Z);
  Writeln(Output, A, Z);
  Z := 10; A := Sneaky(10); A := A * Sneaky(Z);
  Writeln(Output, A, Z);
  Z := 10; A := Sneaky(Z); A := A * Sneaky(10);
  Writeln(Output, A, Z);
end. { SideEffect }
```

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

```
        100          0
          0          0
      10000        -10
```

Pascal においての **副作用** とは、手続き / 関数内で定義されていない変数または変数パラメータへの代入の事を指します。

## 11.3. 前方宣言 (Forward declaration)

手続きや関数は**前方宣言**があれば、その手続きや関数の宣言よりも前で使用できます。

```pascal
  procedure A(v: Integer);
  begin
    B(1); { 呼び出せない }
  end;

  procedure B(v: Integer);
  begin

  end;
```

上記の問題は **forward** 指令を使って前方宣言する事によって回避できます。

```pascal
  procedure B(v: Integer); forward;

  procedure A(v: Integer);
  begin
    B(1); { 呼び出せる }
  end;

  procedure B; { 仮パラメータと結果型はここに書かない }
  begin

  end;
```

Delphi の場合、前方宣言はプログラムファイル (\*.dpr) 内だけで使えます。

**forward** 指令は、ユニットの **interface** セクションでは無効です。**interface** セクション内の手続きと関数のヘッダーは、**forward** 宣言と同様の動作をするので、**implementation** セクションで定義宣言をする必要があります。

```pascal:Unit1.pas
unit Unit1;

interface

// 関数のヘッダー宣言
procedure A(v: Integer);
procedure B(v: Integer);

implementation

procedure A(v: Integer);
begin
  B(1); { 呼び出せる }
end;

procedure B(v: Integer);
begin

end;

end.
```

**See also:**

 - [前方宣言とインターフェイス宣言 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E9%96%A2%E6%95%B0%EF%BC%88Delphi%EF%BC%89#.E5.89.8D.E6.96.B9.E5.AE.A3.E8.A8.80.E3.81.A8.E3.82.A4.E3.83.B3.E3.82.BF.E3.83.BC.E3.83.95.E3.82.A7.E3.82.A4.E3.82.B9.E5.AE.A3.E8.A8.80)
 - [指令 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E6%A7%8B%E6%96%87%E8%A6%81%E7%B4%A0%EF%BC%88Delphi%EF%BC%89#.E6.8C.87.E4.BB.A4)
 - [ヒント指令 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%AE%A3%E8%A8%80%E3%81%A8%E6%96%87%EF%BC%88Delphi%EF%BC%89#.E3.83.92.E3.83.B3.E3.83.88.E6.8C.87.E4.BB.A4)

## (11.4.) インライン展開 (Inline expansion)
手続きや関数は **inline** 指令を使う事でインライン化する事ができます。

```pascal
procedure foo; inline;
```

インライン化とはコンパイラによる最適化手法の一つで、ルーチンを呼び出す側に呼び出されるルーチンのコードを展開する事を指します。

ルーチンの呼び出しに伴うオーバーヘッドを削減する事が狙いなので、大きなルーチンに対して使っても実行形式ファイルが肥大化するだけで効果はありません。繰り返し呼ばれる小さなルーチンで効果を発揮します。

コンパイラ指令 `{$INLINE}` で、インライン化を制御する事が可能です。

| 値 | 定義側の意味 | 呼び出し側の意味 |
|:---|:---|:---|
| `{$INLINE ON}`（デフォルト） |  ルーチンが **inline** 指令によってタグ付けされている場合には、インライン化される。 |  ルーチンは可能であればインラインに展開される。 |
| `{$INLINE AUTO}`  | `{$INLINE ON}` と同じ動作に加え、**inline** でマークされていないコードサイズが 32 バイト以下のルーチンもインライン化される。 | ルーチンのインライン化に影響を及ぼさない。 |
| `{$INLINE OFF}` | ルーチンに **inline** のタグが付いていてもインライン化されない。 | ルーチンはインラインに展開されない。 |

**See also:**

 - [inline 指令の利用 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E9%96%A2%E6%95%B0%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%EF%BC%88Delphi%EF%BC%89#inline_.E6.8C.87.E4.BB.A4.E3.81.AE.E5.88.A9.E7.94.A8)


## (11.5.) 手続き型 (Procedural Types)
手続き型は手続きまたは関数 (ルーチン) を代入できる型です。パラメータと結果の型が同じルーチンを代入できます。他言語では**関数ポインタ (Function Pointer)** とも呼ばれる機能です。

例えば、System.SysUtils では汎用的な手続き型 TProcedure が宣言されています。

```pascal:System.SysUtils.pas
{ Generic procedure pointer }

  TProcedure = procedure;
```

TProcedure 型の変数にはパラメータのない手続きを代入できます。

```pascal:ProcTest.dpr
program Project1;

{$APPTYPE CONSOLE}

procedure A;
begin
  Write('A');
end;

procedure B;
begin
  Write('B');
end;

procedure C;
begin
  Write('C');
end;

type
  TProcedure = procedure; // パラメータのない手続き

var
  i: Integer;
  Procs: array [0..2] of TProcedure;
begin
  Procs[0] := A;
  Procs[1] := B;
  Procs[2] := C;
  for i:=Low(Procs) to High(Procs) do
    Procs[i];
  Writeln;
end.
```

Delphi には手続き型に類似する型として

 - メソッドポインタ型 (**of object**)
 - メソッド参照型 (**reference to**) 

がありますが、どちらもクラスに関連するものなので割愛します。

**See also:**

 - [手続き型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E5%9E%8B)
 - [System.SysUtils.TProcedure  (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TProcedure)
 - [クラスとオブジェクト（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%EF%BC%88Delphi%EF%BC%89)
 - [Delphi での無名メソッド (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/Delphi_%E3%81%A7%E3%81%AE%E7%84%A1%E5%90%8D%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)


### (11.5.1.) 手続き型定数とグローバル手続き型変数の初期化

Delphi で**手続き型定数**を宣言するには、定数の宣言される**手続き型**と互換性のあるルーチンの名前を指定します。

```pascal:ProceduralConstantsTest1.pas
program ProceduralConstantsTest1;

{$APPTYPE CONSOLE}

// 手続き型の定義
type
  TFunction = function(X, Y: Integer): Integer;

var
  i: Integer;

  function Calc(X, Y: Integer): Integer;
  begin

  end; { Calc }


// グローバル手続き型定数
const
  MyFunction1: TFunction = Calc;

// グローバル手続き型変数と初期化
var
  MyFunction2: TFunction = Calc;

  procedure Sub;
//var
//  ローカル手続き型変数は初期化できない
//  MyFunction3: TFunction = Calc;
  begin

  end; { Sub }

begin
  i := MyFunction1(5, 7);
  i := MyFunction2(5, 7);
end.
```

手続き型を定義せずに直接宣言する事もできます。

```pascal:ProceduralConstantsTest2.pas
program ProceduralConstantsTest2;

{$APPTYPE CONSOLE}

var
  i: Integer;

  function Calc(X, Y: Integer): Integer;
  begin

  end; { Calc }

const
  MyFunction1: function(X, Y: Integer): Integer = Calc;

// グローバル手続き変数と初期化
var
  MyFunction2: function(X, Y: Integer): Integer = Calc;

  procedure Sub;
//var
//  ローカル手続き変数は初期化できない
//  MyFunction3: function(X, Y: Integer): Integer = Calc;
  begin

  end; { Sub }

begin
  i := MyFunction1(5, 7);
  i := MyFunction2(5, 7);
end.
```

手続き型変数を使うと次のような事ができます。

```pascal:ProceduralConstantsTest2
program ProceduralTypesFizzBuzz;

{$APPTYPE CONSOLE}

  procedure FizzBuzz(X: Integer);
  begin
    Writeln('FizzBuzz');
  end; { FizzBuzz }

  procedure Fizz(X: Integer);
  begin
    Writeln('Fizz');
  end; { Fizz }

  procedure Buzz(X: Integer);
  begin
    Writeln('Buzz');
  end; { Buzz }

  procedure Num(X: Integer);
  begin
    Writeln(X);
  end; { Num }

var
  i: Integer;
  FB: procedure(X: Integer);
begin
  for i:=1 to 100 do
    begin
      if ((i mod 3) + (i mod 5)) = 0 then
        FB := FizzBuzz
      else if (i mod 3) = 0 then
        FB := Fizz
      else if (i mod 5) = 0 then
        FB := Buzz
      else
        FB := Num;
      FB(i);
    end;
end.
```

**See also:**

 - [手続き定数 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%AE%A3%E8%A8%80%E3%81%95%E3%82%8C%E3%81%9F%E5%AE%9A%E6%95%B0#.E6.89.8B.E7.B6.9A.E3.81.8D.E5.AE.9A.E6.95.B0)

# 索引
[ [← 10. ポインタ型](https://qiita.com/ht_deko/items/745ad4a792d277d33831) ] [ [↑ 目次へ](https://qiita.com/ht_deko/items/e2796788c65c2cda1de5) ] [ [→ 12. テキストファイルの入出力](https://qiita.com/ht_deko/items/391d6c1a53c7aaa3416f) ] [:sushi:](https://qiita.com/ht_deko/items/eb20c1504ea759d04e24)

[^1]: パラメータの型とサイズに応じて値渡しまたは参照渡しになります。複雑なので[ヘルプ](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E5%88%B6%E5%BE%A1#.E5.80.A4.E6.B8.A1.E3.81.97.E3.81.A8.E5.8F.82.E7.85.A7.E6.B8.A1.E3.81.97)で確認してください。基本的には型のサイズが SizeOf(Pointer) よりも大きければ参照渡しとなります。
[^2]: このコード例は『J&W』第 3 版以降、別のものに差し替えられています。
[^3]: Pascal が手続きと関数を明確に区別しているので可能となっています。