パラメータ(Delphi)
手続きと関数:インデックス への移動
このトピックでは、以下の事項について説明します。
- パラメータ セマンティクス
- 文字列パラメータ
- 配列パラメータ
- デフォルト パラメータ
目次
パラメータについて
ほとんどの手続きと関数のヘッダーには、パラメータ リストがあります。 たとえば、次のヘッダーでは
function Power(X: Real; Y: Integer): Real;
パラメータ リストは (X: Real; Y: Integer) です。
パラメータ リストとは、パラメータ宣言をセミコロンで区切って並べ、全体を括弧で囲んだものです。 各宣言はパラメータ名をコンマで区切って並べたもので、通常は、その後にコロンと型識別子が付加されます。また、= 記号によってデフォルト値が指定されることもあります。 パラメータ名は有効な識別子でなければなりません。 宣言の前には、var、const、out のいずれかの予約語を付加できます。 例:
(X, Y: Real)
(var S: string; X: Integer)
(HWnd: Integer; Text, Caption: PChar; Flags: Integer)
(const P; I: Integer)
パラメータ リストには、そのルーチンが呼び出されたときにルーチンに渡すパラメータの個数、順序、および型を指定します。 パラメータのないルーチンの場合は、次のように識別子のリストと括弧を記述しません。
procedure UpdateRecords;
begin
...
end;
パラメータ名(最初の例では X と Y)は、手続きまたは関数の本体内でローカル変数として使用することができます。 パラメータ名を手続きまたは関数本体のローカル宣言部で再び宣言しないでください。
パラメータ セマンティクス
パラメータは、次のようにさまざまな分類ができます。
- すべてのパラメータは、値パラメータ、変数パラメータ、定数パラメータ、out パラメータのいずれかに分類されます。 値パラメータがデフォルトです。予約語 var、const、out は、それぞれ変数パラメータ、定数パラメータ、out パラメータを表します。
- 値パラメータには必ず型を指定します。これに対して、定数パラメータ、変数パラメータ、out パラメータは、型を指定しても型なしでもかまいません。
- 配列パラメータには特別な規則が適用されます。
ファイルと、ファイルが含まれる構造化型のインスタンスは変数パラメータ(var)としてしか渡せません。
値パラメータと変数パラメータ
大部分のパラメータは、値パラメータ(デフォルト)か変数パラメータ(var)のどちらかになります。 値パラメータは値で渡され、変数パラメータは参照で渡されます。 これらを具体的に確認するため、次に 2 つの関数を例に示します。
function DoubleByValue(X: Integer): Integer; // X is a value parameter
begin
X := X * 2;
Result := X;
end;
function DoubleByRef(var X: Integer): Integer; // X is a variable parameter
begin
X := X * 2;
Result := X;
end;
どちらの関数も返す結果は同じですが、渡される変数の値を変更できるのは 2 番めの DoubleByRef 関数だけです。 これらの関数を次のように呼び出してみます。
var
I, J, V, W: Integer;
begin
I := 4;
V := 4;
J := DoubleByValue(I); // J = 8, I = 4
W := DoubleByRef(V); // W = 8, V = 8
end;
DoubleByValue に渡した変数 I の値は、このコードの実行後も代入時点と変わりません。 しかし、DoubleByRef に渡した変数 V は、代入時とは異なる値になっています。
値パラメータは、手続きまたは関数の呼び出し時に渡す値に初期化されるローカル変数のような働きをします。 変数を値パラメータとして渡すと、手続きや関数はそのコピーを作成します。そのため、コピーに変更を加えても元の変数の値は変わらず、プログラムの実行が呼び出し元に戻った時点でコピーの内容は失われます。
一方、変数パラメータはコピーではなくポインタのような働きをします。 関数や手続きの本体部分でパラメータに加えた変更は、プログラムの実行が呼び出し元に戻り、パラメータ名自体がスコープ外になっても失われません。
同一の変数が複数の var パラメータに渡された場合であっても、コピーは作成されません。 次に例を使って説明します。
procedure AddOne(var X, Y: Integer);
begin
X := X + 1;
Y := Y + 1;
end;
var I: Integer;
begin
I := 1;
AddOne(I, I);
end;
このコードの実行後、I の値は 3 になります。
宣言の中で var パラメータが指定されたルーチンを呼び出すときは、変数、型付き定数({$J+} が指定されている場合)、逆参照ポインタ、フィールド、インデックス付きの変数など、代入可能な式を渡さなければなりません。 前述の例では、DoubleByRef(7) はエラーになりますが、DoubleByValue(7) は正しく処理されます。
DoubleByRef(MyArray[I]) のように var パラメータに渡されたインデックスまたはポインタの逆参照は、ルーチンの実行前に一度評価されます。
定数パラメータ
定数パラメータ(const)は、ローカル定数や読み取り専用変数のようなものです。 定数パラメータは値パラメータに似ていますが、手続きや関数の本体で定数パラメータに値を代入したり、定数パラメータを別のルーチンに変数パラメータ(var)として渡すことはできません。 ただし、オブジェクトの参照を定数パラメータとして渡すときは、そのオブジェクトのプロパティに変更を加えることは可能です。
構造化型パラメータや文字列型パラメータに対して const を指定すると、コンパイラではコードを最適化できます。 また、パラメータが別のルーチンに誤って参照渡しされるのを防ぐこともできます。
たとえば、以下に示すのは、SysUtils ユニットに含まれている CompareStr 関数のヘッダーです。
function CompareStr(const S1, S2: string): Integer;
S1 と S2 の値は CompareStr の本体で変更されないので、どちらも定数パラメータとして宣言できます。
定数パラメータを関数に渡す際は、参照渡しでも値渡しでもよく、そのどちらになるかは、使用する個々のコンパイラによって異なります。 const キーワードと一緒に [Ref] 属性を使用すると、コンパイラに定数パラメータを強制的に参照渡しさせることができます。
以下の例では、'[Ref] 属性は const キーワードの前でも後でも指定できることを示しています:'
function FunctionName(const [Ref] parameter1: Class1Name; [Ref] const parameter2: Class2Name);
out パラメータ
out パラメータは、変数パラメータと同じように参照によって渡されます。 しかし、out パラメータの最初の参照変数の値は、呼び出し先ルーチンに渡る前にいったん破棄されます。 out パラメータは結果を受け取ることしかできません。あくまで結果を保存する関数や手続きであって、入力を提供することはありません。
次に、手続きのヘッダーの例を示します。
procedure GetInfo(out Info: SomeRecordType);
GetInfo 手続きを呼び出すときは、SomeRecordType 型の変数を渡す必要があります。
var MyRecord: SomeRecordType;
...
GetInfo(MyRecord);
ただし、MyRecord を使用して、GetInfo 手続きに特定のデータを渡すわけではありません。MyRecord は、GetInfo によって生成される情報を格納するコンテナです。 GetInfo 手続きが呼び出されると、MyRecord によって使用されているメモリがただちに解放され、その後でプログラム制御が手続きに渡されます。
out パラメータは、COM などの分散オブジェクト モデルで多用されます。 また、初期化されていない変数を関数や手続きに渡す場合にも out パラメータを使用できます。
型なしパラメータ
var、const、および out パラメータの宣言時には、型の指定を省略できます (値パラメータは必ず型を指定する必要があります)。 例:
procedure TakeAnything(const C);
このコードでは、任意の型のパラメータを受け付ける TakeAnything という手続きを宣言しています。 このようなルーチンを呼び出すときに、数値や型なしの数値定数を渡すことはできません。
手続きや関数の本体内では、型なしパラメータはどのような型とも互換性がありません。 型なしパラメータに対して操作を実行するときはキャストする必要があります。 通常、コンパイラは型なしパラメータに対する操作が有効かどうかを確認できません。
次の例では、指定したバイト数の任意の 2 つの変数を比較する関数 Equal で型なしパラメータを使用しています。
function Equal(var Source, Dest; Size: Integer): Boolean;
type
TBytes = array[0..MaxInt - 1] of Byte;
var
N : Integer;
begin
N := 0;
while (N < Size) and (TBytes(Dest)[N] = TBytes(Source)[N]) do
Inc(N);
Equal := N = Size;
end;
次のような宣言が行われているとします。
type
TVector = array[1..10] of Integer;
TPoint = record
X, Y: Integer; // Integer occupies 4 bytes. Therefore 8 bytes in a whole
end;
var
Vec1, Vec2: TVector;
N: Integer;
P: TPoint;
この場合、次のような Equal 呼び出しが可能です。
Equal(Vec1, Vec2, SizeOf(TVector)); // compare Vec1 to Vec2
Equal(Vec1, Vec2, SizeOf(Integer) * N); // compare first N
// elements of Vec1 and Vec2
Equal(Vec1[1], Vec1[6], SizeOf(Integer) * 5); // compare first 5 to
// last 5 elements of Vec1
Equal(Vec1[1], P, 8); // compare Vec1[1] to P.X and Vec1[2] to P.Y
// each Vec1[x] is integer and occupies 4 bytes
untyped
の var パラメータを許容しません。
たとえば、TObject Obj
をキャストするには、型を付けなければなりません:
procedure FreeAndNil(var Obj: TObject);
文字列パラメータ
短い文字列型のパラメータをとるルーチンを宣言するとき、そのパラメータ宣言では長さを指定できません。 つまり、次のような宣言はコンパイル エラーになります。
procedure Check(S: string[20]); // syntax error
ただし、次の宣言は有効です。
type TString20 = string[20];
procedure Check(S: TString20);
長さの変化する短い文字列型のパラメータをとるルーチンの宣言には、次のように、特別な識別子である OpenString を使用できます。
procedure Check(S: OpenString);
コンパイラ指令 {$H} と {$P+} がどちらも有効になっている場合は、パラメータの宣言において string は OpenString と同じ意味です。
短い文字列、OpenString、$H、および $P は、下位互換性のためにのみサポートされています。 新しいコードで長い文字列を使用すれば、以上のような点を考慮する必要がなくなります。
配列パラメータ
配列型のパラメータをとるルーチンを宣言するとき、そのパラメータ宣言でインデックスの型を指定することはできません。 次の宣言があるとします。
procedure Sort(A: array[1..10] of Integer) // syntax error
この宣言はコンパイルエラーになります。 しかし、
type TDigits = array[1..10] of Integer;
procedure Sort(A: TDigits);
この文は有効です。 オープン配列パラメータを使用する方法もあります。
Delphi 言語には動的配列用の値のセマンティクスが実装されていないので、ルーチン内の値パラメータが動的配列の完全なコピーを表現することはありません。 次に例を示します。
type
TDynamicArray = array of Integer;
procedure p(Value: TDynamicArray);
begin
Value[0] := 1;
end;
procedure Run;
var
a: TDynamicArray;
begin
SetLength(a, 1);
a[0] := 0;
p(a);
Writeln(a[0]); // Prints '1'
end;
ルーチン p 内の Value[0] への代入によって、Value が値渡しのパラメータであるにもかかわらず、呼び出し元の動的配列の内容が変更される点に注意してください。 動的配列の完全なコピーが必要な場合は、標準手続きの Copy を使って動的配列の値コピーを作成します。
オープン配列パラメータ
オープン配列パラメータを使用すると、サイズの異なる配列を同じ手続きや関数に渡すことができます。 オープン配列パラメータを使用するルーチンを定義するには、パラメータ宣言で array[X..Y] of type という構文のかわりに array of type という構文を使用します。 例:
function Find(A: array of Char): Integer;
このコードでは、任意のサイズの文字配列をとり、整数を返す Find という関数を宣言しています。
メモ: オープン配列パラメータの構文は動的配列の構文に似ていますが、両者は同じではありません。 前者の例は、Char 要素の任意の配列をとる関数を作成します。これには動的配列も含まれますが、動的配列に限定されるわけではありません。 動的配列だけをとるパラメータを宣言するには、次のように型識別子を指定する必要があります。
type TDynamicCharArray = array of Char;
function Find(A: TDynamicCharArray): Integer;
ルーチンの本体内では、オープン配列パラメータに対して以下の規則が適用されます。
- インデックスは必ず 0 から始まります。 したがって、1 番めの要素は 0 で、2 番めの要素は 1 になります。 標準関数の Low と High は、それぞれ 0 と(Length - 1)を返します。 SizeOf 関数は、ルーチンに渡される実際の配列のサイズを返します。
- オープン配列パラメータは、要素単位でのみアクセスできます。 オープン配列パラメータ全体に対して一度に代入を実行することはできません。
- 他の手続きおよび関数には、オープン配列パラメータか型なしの var パラメータとしてのみ渡すことができます。 オープン配列パラメータは SetLength には渡せません。
- 配列ではなく、オープン配列パラメータの基底型の変数を渡すことができます。 この場合、長さ 1 の配列として扱われます。
配列をオープン配列の値パラメータとして渡すと、コンパイラはルーチンのスタック フレームにそのローカル コピーを作成します。 このため、大きな配列を渡す場合は、スタックがオーバーフローしないように注意してください。
次の例では、オープン配列パラメータを使用して、実数型配列の各要素に 0 を代入する Clear という手続きと、実数型配列の全要素の合計を計算する Sum という関数を定義しています。
procedure Clear(var A: array of Real);
var
I: Integer;
begin
for I := 0 to High(A) do A[I] := 0;
end;
function Sum(const A: array of Real): Real;
var
I: Integer;
S: Real;
begin
S := 0;
for I := 0 to High(A) do S := S + A[I];
Sum := S;
end;
オープン配列パラメータを使ったルーチンには、オープン配列コンストラクタを渡すことができます。
型可変オープン配列パラメータ
型可変オープン配列パラメータを使用すると、同一の手続きや関数にさまざまな型の式が格納された配列を渡すことができます。 型可変オープン配列パラメータを使用するルーチンを定義するには、パラメータの型として array of const
を指定します。 そのため、次の宣言は
procedure DoSomething(A: array of const);
これは、さまざまな型の要素が格納された配列を操作できる DoSomething
という手続きを宣言しています。
array of const
は、array of TVarRec
と同じ意味を持ちます。 System.TVarRec は System
ユニットで宣言されており、整数、論理値、文字、実数、文字列、ポインタ、クラス、クラス参照、インターフェイス、バリアントの各型の値を格納できる可変部分を持つレコードを表します。 TVarRec
の VType
フィールドは、配列の各要素の型を表すフィールドです。 一部の型は、値ではなくポインタとして渡されます。特に、文字列は Pointer 型として渡されるため、string 型に型キャストする必要があります。
次の Win32 の例では、渡された各要素について文字列表現を作成し、その結果を連結して 1 つの文字列にする関数で型可変オープン配列パラメータを使用しています。 この関数の中で使用している文字列処理ルーチンは、SysUtils
で定義されているものです。
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;
この関数は、オープン配列コンストラクタを使って呼び出すことができます。 例:
MakeStr(['test', 100, ' ', True, 3.14159, TForm])
文字列 test100 T3.14159TForm
を戻します。
デフォルト パラメータ
手続きや関数のヘッダーで、デフォルトのパラメータ値を指定できます。 デフォルト値を指定できるのは、const パラメータと値パラメータだけです。 デフォルト値を指定するには、パラメータ宣言の末尾に = に続けてパラメータに代入可能な型の定数式を指定します。
たとえば、次のような宣言があるとします。
procedure FillArray(A: array of Integer; Value: Integer = 0);
次の 2 つの手続き呼び出しは、どちらも同じ意味になります。
FillArray(MyArray);
FillArray(MyArray, 0);
複数のパラメータを一度に宣言する場合には、デフォルト値は指定できません。 したがって、次の宣言は有効です。
function MyFunction(X: Real = 3.5; Y: Real = 3.5): Real;
次の宣言は有効ではありません。
function MyFunction(X, Y: Real = 3.5): Real; // syntax error
デフォルト値を指定するパラメータは、パラメータ リストの末尾に置かなければなりません。 つまり、あるパラメータにデフォルト値を指定したら、それ以降のパラメータにはすべてデフォルト値を指定する必要があります。 したがって、次の宣言は無効です。
procedure MyProcedure(I: Integer = 1; S: string); // syntax error
手続き型で指定したデフォルト値は、実際のルーチンの中で指定したデフォルト値よりも優先されます。 たとえば、次のように宣言されているとします。
type TResizer = function(X: Real; Y: Real = 1.0): Real;
function Resizer(X: Real; Y: Real = 2.0): Real;
var
F: TResizer;
N: Real;
この場合、結果は、
F := Resizer;
F(N);
Resizer に渡される値 (N, 1.0) になります。
デフォルト パラメータとして指定できるのは、定数式で指定可能な値だけです。 したがって、動的配列、手続き、クラス、クラス参照、インターフェイスの各型のパラメータに対しては、nil 以外のデフォルト値は指定できません。 レコード、バリアント、ファイル、静的配列、オブジェクトの各型のパラメータに対しては、デフォルト値は指定できません。
デフォルトのパラメータとオーバーロード関数
オーバーロード ルーチンでデフォルトのパラメータ値を使用する場合は、パラメータ宣言があいまいにならないように注意してください。 次に例を示します。
procedure Confused(I: Integer); overload;
...
procedure Confused(I: Integer; J: Integer = 0); overload;
...
Confused(X); // Which procedure is called?
実際にはどちらの手続きも呼び出されません。 このコードではコンパイル エラーが発生します。
forward 宣言とインターフェイス宣言でのデフォルト パラメータ
forward 宣言されているルーチンまたはユニットの interface セクションで宣言されているルーチンでデフォルトのパラメータ値を指定する場合、デフォルトのパラメータ値は、forward 宣言部分またはインターフェイス部で指定する必要があります。 このような場合、定義宣言部分(implementation セクション)ではデフォルト値を指定する必要はありませんが、指定する場合は、forward 宣言部分または interface セクションでの指定と完全に同じ値を指定しなければなりません。