構造化型(Delphi)

提供: RAD Studio
移動先: 案内検索

データ型、変数、定数:インデックス への移動

構造化型のインスタンスには、複数の値が格納されます。 構造化型には、集合型、配列型、レコード型、ファイル型、クラス型、クラス参照型、およびインターフェイス型があります。 集合型(順序値だけを保持する)以外の構造化型には、他の構造化型を入れることができます。型の構造化のレベルに制限はありません。

このトピックでは、次の構造化型について説明します。

  • 集合
  • 配列型(静的配列、動的配列など)
  • レコード
  • ファイル型

構造化型のアラインメント

デフォルトでは、構造化型の値は、アクセスをより速くするために、ワード境界またはダブルワード境界でアラインメントされます。

ただし、構造化型を宣言する際に予約語 packed を付けることで、バイト アラインメントを指定できます。予約語 packed は、データ格納領域の圧縮を指定するものです。以下に宣言例を示します。

 type TNumbers = packed array [1..100] of Real;

packed を習慣的に使用すると、他の言語やプラットフォームとの互換性を妨げるおそれがあり、データ アクセスが遅くなり、文字配列の場合には型の互換性に影響を及ぼすため、お勧めしません。 詳細については、「メモリ管理」および「型仕様が共通なフィールドの暗黙的パッキング」を参照してください。

集合

集合は、同一の順序型の値の集まりです。値に順序はありません。また、同じ値を集合に 2 度格納することに意味はありません。

集合型の値の範囲は、基底型と呼ばれる特定の順序型のべき集合です。つまり、集合型がとりうる値は、基底型のとりうる値のすべての部分集合(空集合を含む)です。基底型がとりうる値の数が 256 を超えることはできません。順序値は、0 ~ 255 の範囲内である必要があります。次の形式の構文では、

set of baseType

baseType は適切な順序型であり、1 つの集合型を識別します。

基底型のサイズに制限があるので、通常、集合型は部分範囲を指定して定義されます。たとえば、次の宣言があるとします。

 type
   TSomeInts = 1..250;
   TIntSet = set of TSomeInts;

これは 1 ~ 250 の整数の集まりを値とする TIntSet という集合型を作成します。次のように宣言しても同じことができます。

 type TIntSet = set of 1..250;

この宣言を使用すると、以下の集合を作成できます。

 var Set1, Set2: TIntSet;
     ...
     Set1 := [1, 3, 5, 7, 9];
     Set2 := [2, 4, 6, 8, 10]

set of ... 構文を変数の宣言で直接使用することもできます。

 var MySet: set of 'a'..'z';
     ...
     MySet := ['a','b','c'];
メモ: 詳細については、次の警告メッセージを参照してください: W1050 設定式で WideChar がバイト文字に変換されました (Delphi)

集合型には、次の例もあります。

set of Byte
set of (Club, Diamond, Heart, Spade)
set of Char;

in 演算子は、ある値が集合に含まれているかどうかをテストします。

 if 'a' in MySet then ... { do something } ;

すべての集合型は、[] で表される空集合を保持できます。詳細については、「式(Delphi)」の "集合構成子" および "集合演算子" を参照してください。

配列型

配列型は、基底型と呼ばれる同じ型の要素のインデックス付きの集まりを表します。配列の要素にはすべて一意のインデックスが付けられているため、集合型とは異なり、同一の値を 2 回以上格納することには意味があります。配列は静的にも動的にも割り当てることができます。

静的配列

静的な配列型は、次の形式の構文で表されます。

array[indexType1, ..., indexTypen] of baseType;

indexType は、範囲が 2 GB を超えない順序型です。indexTypes は配列のインデックス付けに使用されるので、配列に格納できる要素の数は各 indexTypes のサイズの積によって決定されます。通常、indexTypes には整数の部分範囲が使用されます。

最も単純な 1 次元配列の場合、indexType は 1 つだけです。以下に例を示します。

 var MyArray: array [1..100] of Char;

これは文字値 100 個の配列を格納する MyArray という変数を宣言します。このように宣言されている場合、MyArray[3]MyArray の 3 番目の文字を表します。作成した静的配列の一部の要素に値を代入しなかった場合、未使用の要素にもメモリは割り当てられます。ただし、初期化されていない変数と同様に、これらの要素の内容はランダムなデータになります。

多次元配列は配列の配列です。以下に例を示します。

 type TMatrix = array[1..10] of array[1..50] of Real;

これは次と同じ意味になります。

 type TMatrix = array[1..10, 1..50] of Real;

どちらの方法で宣言しても、TMatrix は 500 個の実数値の配列を表します。TMatrix 型の変数 MyMatrix にインデックス付けする方法は、MyMatrix[2,45]MyMatrix[2][45] の 2 種類があります。また、次のように指定したとします。

 packed array[Boolean, 1..10, TShoeSize] of Integer;

これは次と同じ意味になります。

 packed array[Boolean] of packed array[1..10] of packed array[TShoeSize] of Integer;

標準関数の LowHigh は、配列型の識別子と変数にも使用できます。これらの関数は配列の 1 番目のインデックス型の下限と上限を返します。標準関数 Length は配列の第 1 次元の要素の数を返します。

Char 値の静的な 1 次元パック配列は、パック文字列と呼ばれます。 パック文字列型は文字列型と互換性があり、要素数が同じである他のパック文字列型とも互換性があります。 「型の互換性と同一性(Delphi)」を参照してください。

array[0..x] of Char という形式の配列型は、インデックスが 0 から始まる文字配列と呼ばれます。インデックスが 0 から始まる文字配列は、NULL 終端文字列の格納に使用され、PChar 値と互換性があります。「文字列型(Delphi)」の "NULL 終端文字列の取り扱い" を参照してください。

動的配列

動的配列はサイズや長さが固定されていません。その代わり、動的配列のメモリは、配列に値を代入したときや SetLength 手続きに配列を渡したときに再割り当てされます。動的配列型は、次の形式の構文で表されます。

array of baseType

以下に例を示します。

 var MyFlexibleArray: array of Real;

これは実数の 1 次元動的配列を宣言しています。この宣言では MyFlexibleArray のメモリは割り当てられません。配列をメモリ内に作成するには SetLength を呼び出します。たとえば、上記の宣言がある場合、

 SetLength(MyFlexibleArray, 20);

ここでは、インデックスが 0 ~ 19 の実数 20 個の配列が割り当てられます。動的配列のメモリを割り当てるには、この他に、次のように配列コンストラクタを呼び出す方法もあります。

 type
   TMyFlexibleArray = array of Integer;
 
 begin
   MyFlexibleArray := TMyFlexibleArray.Create(1, 2, 3 {...});
 end;

ここでは、要素 3 個分のメモリが割り当てられ、それぞれの要素に指定の値が代入されます。

配列コンストラクタと同様に、次のような配列定数式から動的配列を初期化することもできます。

  procedure MyProc;
  var
    A: array of Integer;
  begin
    A := [1, 2, 3];
  end;

配列コンストラクタとは異なり、配列定数は無名の動的配列型に直接適用することができる点に注意してください。この構文は動的配列に固有のものです。この手法を他の配列型に適用すると、結果的に定数が集合として解釈される可能性があり、型に互換性がないというコンパイル エラーが発生することになります。

動的配列のインデックスは、常に 0 から始まる整数です。

動的配列変数は暗黙のポインタであり、長い文字列型と同じ参照カウントの方法を使って管理されます。動的配列の割り当てを解除するには、配列を参照する変数に nil を代入するか、その変数を Finalize に渡します。どちらの方法でも、この配列への参照が他にない場合は、配列が破棄されます。動的配列は、参照カウントが 0 まで下がったときに、自動的に解放されます。長さが 0 である動的配列の値は nil です。動的配列変数に逆参照演算子(^)は使用できません。また、動的配列変数を New 手続きまたは Dispose 手続きに渡すことはできません。

XY が同じ動的配列型の変数である場合、X := Y は、XY と同じ配列を指すようにします。この処理を実行する前に、X 用にメモリを割り当てる必要はありません。文字列や静的配列とは異なり、コピーオンライトは動的配列には採用されておらず、動的配列が書き込み時に自動的にコピーされることはありません。たとえば、次のコードを実行したとします。

 var
   A, B: array of Integer;
   begin
     SetLength(A, 1);
     A[0] := 1;
     B := A;
     B[0] := 2;
   end;

実行後の A[0] の値は 2 です。AB が静的配列であれば、A[0] は 1 のままです。

インデックス付けによる動的配列への代入(MyFlexibleArray[2] := 7 など)を実行しても、配列の再割り当ては行われません。範囲外のインデックスを使用しても、コンパイル時には報告されません。

それに対して動的配列の独立したコピーを作成する場合は、次のようにグローバル関数 Copy を使用する必要があります。

 var
   A, B: array of Integer;
 begin
   SetLength(A, 1);
   A[0] := 1;
   B := Copy(A);
   B[0] := 2; { B[0] <> A[0] }
 end;

動的配列変数の比較では、配列の値ではなくポインタが比較されます。たとえば、次のコードを実行したとします。

 var
   A, B: array of Integer;
 begin
    SetLength(A, 1);
    SetLength(B, 1);
    A[0] := 2;
    B[0] := 2;
 end;

A = BFalse を返しますが、A[0] = B[0]True を返します。

動的配列を切り捨てるには、SetLength 手続きまたは Copy 関数に配列変数を渡し、結果を配列変数に代入します。通常は SetLength 手続きの方が高速です。たとえば、A が動的配列である場合、次のいずれも、A の先頭から 20 個の要素を除くすべての要素を切り捨てます。

 SetLength(A, 20)
 A := Copy(A, 0, 20)

割り当て済みの動的配列は、標準関数 LengthHigh、および Low に渡すことができます。Length は配列要素の数を返し、High は配列のインデックスの上限、つまり Length - 1 を返し、Low は 0 を返します。配列の要素数が 0 の場合、High は -1 を返すので、High < Low という特異な結果になります。

メモ: 一部の関数と手続きの宣言では、配列パラメータがインデックス型を指定せずに array of baseType と表されます。たとえば、function CheckStrings(A: array of string): Boolean;

これは、配列のサイズ、インデックス付けの方法、割り当てが静的か動的かに関係なく、指定された基底型のすべての配列をその関数が処理できることを示します。

多次元動的配列

多次元動的配列を宣言するには、array of ... 構文を重ねて使用します。以下に例を示します。

 type TMessageGrid = array of array of string;
 var Msgs: TMessageGrid;

これは文字列の 2 次元配列を宣言します。この配列をインスタンス化するには、2 つの整数引数を付けて SetLength を呼び出します。たとえば、IJ が整数の値を持つ変数であるとします。

 SetLength(Msgs,I,J);

これは、I x J 配列を割り当て、Msgs[0,0] は配列の要素の 1 つを表します。

特定の次元の配列の長さがすべて同じではない多次元動的配列を作成することもできます。まず SetLength を呼び出し、配列の最初の n 次元を指定するパラメータを渡します。以下に例を示します。

 var Ints: array of array of Integer;
 SetLength(Ints,10);

これは Ints に 10 行を割り当てますが、列は割り当てません。次に、列を 1 つずつ割り当て、それぞれ異なる長さを指定します。以下に例を示します。

 SetLength(Ints[2], 5);

これは、Ints の第 3 列の長さを整数 5 つ分にします。この時点で、他の列をまだ割り当てていない場合でも、第 3 列に値を代入できます。たとえば、Ints[2,4] := 6 のようにします。

次の例では、動的配列と SysUtils ユニットで宣言されている IntToStr 関数を使用して、三角形の文字列行列を作成します。

 var
   A : array of array of string;
   I, J : Integer;
 begin
   SetLength(A, 10);
   for I := Low(A) to High(A) do
   begin
     SetLength(A[I], I);
     for J := Low(A[I]) to High(A[I]) do
       A[I,J] := IntToStr(I) + ',' + IntToStr(J) + ' ';
     end;
   end;

配列の型と代入

配列は同一の型である場合にのみ代入互換です。Delphi 言語では型の名前に基づいて型の一致を判定するので、次のコードはコンパイルできません。

 var
   Int1: array[1..10] of Integer;
   Int2: array[1..10] of Integer;
       ...
   Int1 := Int2;

代入するには、次のように変数を宣言します。

 var Int1, Int2: array[1..10] of Integer;

または

 type IntArray = array[1..10] of Integer;
 var
    Int1: IntArray;
    Int2: IntArray;

動的配列に対する文字列風の操作をサポート

動的配列を文字列と同じように操作することができます。以下に例を示します。

var
  A: array of integer;
  B: TBytes = [1,2,3,4]; //Initialization can be done from declaration
begin
  ...
  A:=[1,2,3]; // assignation using constant array
  A:=A+[4,5]; // addition - A will become [1,2,3,4,5]
  ...
end;

文字列操作に似た働きをするサポート ルーチン

Delphi 組み込みルーチンの一部では、文字列の操作に加えて、動的配列の操作もサポートしています。

System.Insert Insert 関数では、インデックスの位置を先頭に動的配列を挿入します。これは、変更後の配列を返します。以下に例を示します。

  
var
  A: array of integer;
begin
  ...
  A:=[1,2,3,4];
  Insert(5,A,2); // A will become [1,2,5,3,4]
  ...
end;

System.Delete

Delete 関数は動的配列から要素を削除し、変更後の配列を返します。以下に例を示します。

  
var
  A: array of integer;
begin
  ...
  A:=[1,2,3,4];
  Delete(A,1,2); //A will become [1,4]
  ...
end;

System.Concat Concat 関数を使用すると、2 つの異なる動的配列を連結できます:

  
  A := Concat([1,2,3],[4,5,6]); //A will become [1,2,3,4,5,6]

レコード型(従来型)

レコードは、他の言語では構造体とも呼ばれ、複数の型を持つ要素の集合を表します。各要素はフィールドと呼ばれ、レコード型の宣言では各フィールドの名前と型を指定します。レコード型の宣言の構文を次に示します。

 type recordTypeName = record
   fieldList1: type1;
    ...
   fieldListn: typen;
 end

recordTypeName には有効な識別子を指定し、フィールド型には型を指定し、フィールドリストには有効な識別子または識別子のコンマ区切りリストを指定します。末尾のセミコロンはオプションです。

たとえば、次の宣言は TDateRec というレコード型を作成します。

 type
   TDateRec = record
     Year: Integer;
     Month: (Jan, Feb, Mar, Apr, May, Jun,
             Jul, Aug, Sep, Oct, Nov, Dec);
     Day: 1..31;
   end;

TDateRec 型の各変数には、Year という整数値、Month という列挙型の値、および Day という 1 ~ 31 の整数値の 3 つのフィールドがあります。YearMonth、および DayTDateRec のフィールド指定子であり、変数と同様に機能します。ただし、TDateRec 型を宣言しても、YearMonth、および Day の各フィールドにメモリは割り当てられません。メモリは、次のようにレコードのインスタンスを作成すると割り当てられます。

 var Record1, Record2: TDateRec;

この変数宣言では、Record1 および Record2 という TDateRec 型の 2 つのインスタンスが作成されます。

レコードのフィールドにアクセスするには、フィールド指定子をレコード名で限定します。

 Record1.Year := 1904;
 Record1.Month := Jun;
 Record1.Day := 16;

with 文を使用する方法もあります。

 with Record1 do
 begin
   Year := 1904;
   Month := Jun;
   Day := 16;
 end;

この時点で Record1 のフィールドの値を Record2 にコピーできます。

 Record2 := Record1;

フィールド指定子のスコープはフィールドが存在するレコードに限定されているので、フィールド指定子と他の変数の間で名前が競合することはありません。

レコード型を定義するのではなく、変数の宣言で record ... 構文を直接使用することもできます。

 var S: record
   Name: string;
   Age: Integer;
 end;

ただし、レコード型の長所は類似する変数のグループを繰り返しコーディングしなくても済むことであるので、このように宣言するとレコード型を使用する意味がほとんどなくなります。また、この種のレコードを別々に宣言すると、たとえ構造がまったく同じであっても、代入互換にはなりません。

レコードの可変部分

レコード型は可変部分を持つことができます。可変部分は case 文に似ています。可変部分はレコード宣言で他のフィールドよりも後に記述する必要があります。

可変部分を持つレコード型を宣言するには、次の構文を使用します。

 type recordTypeName = record
   fieldList1: type1;
    ...
   fieldListn: typen;
 case tag: ordinalType of
   constantList1: (variant1);
    ...
   constantListn: (variantn);
 end;

宣言のうち、予約語 case までの前半部分は、標準的なレコード型と同じです。宣言の残り、つまり case から末尾のオプションのセミコロンまでが可変部分と呼ばれます。可変部分には以下の規則が適用されます。

  • tag はオプションで、任意の有効な識別子を使用できる。tag を省略する場合は、後のコロン(:)も省略する。
  • ordinalType には、順序型の型識別子を指定する。
  • constantList は、ordinalType 型の値を表す定数か、そのような定数のコンマ区切りリストである。結合された constantLists には同じ値を 2 回以上記述することはできない。
  • variant は、レコード型の主部分の fieldList: type という構文に似ている、セミコロンで区切られた宣言のリストである。つまり、可変部は次の形式をとる。
 fieldList1: type1;
   ...
 fieldListn: typen;

ここで、fieldList は有効な識別子または識別子のコンマ区切りリスト、各 type は型です。最後のセミコロンは省略できます。type には、長い文字列、動的配列、バリアント(Variant 型)、インターフェイス型は指定できません。また、これらの型を含む構造化型も指定できません。ただし、これらの型へのポインタは指定できます。

可変部分を持つレコードは、構文上は複雑ですが、その意味はたいへん単純です。レコードの可変部分には、メモリの同じ領域を共有する複数の可変部分が含まれます。いつでも任意の可変部の任意のフィールドを読み取ったり、書き込むことができます。ただし、ある可変部のフィールドに書き込んでから、別の可変部のフィールドに書き込むと、最初のデータが上書きされる場合があります。タグが存在する場合、レコードの非可変部分では ordinalType 型の特別なフィールドとして機能します。

可変部分を使用する目的は 2 つあります。第一に、さまざまな種類のデータをフィールドに格納するレコード型を作成するときに、単一のインスタンスですべてのフィールドを使用する必要が絶対にないことがわかっているとします。以下に例を示します。

 type
   TEmployee = record
   FirstName, LastName: string[40];
   BirthDate: TDate;
   case Salaried: Boolean of
     True: (AnnualSalary: Currency);
     False: (HourlyWage: Currency);
 end;

すべての従業員に年俸または時給が存在するが、両方とも存在する従業員はいない、ということに注意してください。したがって、TEmployee のインスタンスを作成するときに、両方のフィールドのためのメモリを割り当てる理由はありません。この場合、2 つの可変部の相違はフィールド名だけですが、フィールドの型が異なる場合もあります。より複雑な例を示します。

 type
   TPerson = record
   FirstName, LastName: string[40];
   BirthDate: TDate;
   case Citizen: Boolean of
     True: (Birthplace: string[40]);
     False: (Country: string[20];
             EntryPort: string[20];
             EntryDate, ExitDate: TDate);
   end;
 
 type
   TShapeList = (Rectangle, Triangle, Circle, Ellipse, Other);
   TFigure = record
     case TShapeList of
       Rectangle: (Height, Width: Real);
       Triangle: (Side1, Side2, Angle: Real);
       Circle: (Radius: Real);
       Ellipse, Other: ();
   end;

レコード型の各インスタンスについて、コンパイラは最大の可変部のすべてのフィールドを格納できるだけのメモリを割り当てます。オプションの tag と constantLists(最後の例の RectangleTriangle など)は、コンパイラによるフィールドの保守には影響しません。これらは、プログラマの便宜のために用意されています。

可変部分を使用する 2 つめの理由は、コンパイラが型キャストを許可しないような場合でも、同じデータを異なる型のデータとして扱うことにあります。たとえば、ある可変部の 1 つめのフィールドが 64 ビットの Real 型であり、別の可変部の 1 つめのフィールドが 32 ビットの Integer 型である場合は、Real 型のフィールドに値を割り当ててから Integer 型のフィールドの値を読み取ることで、先頭の 32 ビットを調べることができます。読み取った値は、たとえば整数型のパラメータをとる関数に渡すことができます。

レコード型(高度)

Delphi 言語では従来のレコード型のほかに、それよりも複雑な "クラス類似" レコード型を使用できます。 レコードは、フィールドのほかにプロパティとメソッド(コンストラクタを含む)、クラス プロパティ、クラス メソッド、クラス フィールド、およびネストした型を持つ場合があります。 これらの詳細は、「クラスとオブジェクト(Delphi)」のトピックを参照してください。 "クラス類似" 機能を持つレコード型の定義例を示します。

 type
   TMyRecord = record
     type
       TInnerColorType = Integer;
     var
       Red: Integer;
     class var
       Blue: Integer;
     procedure printRed();
     constructor Create(val: Integer);
     property RedProperty: TInnerColorType read Red write Red;
     class property BlueProp: TInnerColorType read Blue write Blue;
 end;
 
 constructor TMyRecord.Create(val: Integer);
 begin
   Red := val;
 end;
 
 procedure TMyRecord.printRed;
 begin
   Writeln('Red: ', Red);
 end;

レコードにはクラスの機能の多くがサポートされるようになりましたが、クラスとレコードには次に示す重要な相違があります。

  • レコードは継承をサポートしません。
  • レコードは可変部分を持つ場合がありますが、クラスにはありません。
  • レコードは値型なので、グローバルに宣言されるか、New および Dispose 関数を使って明示的に割り当てられていない限り、代入ではコピーされ、値によって受け渡され、スタックに割り当てられます。クラスは参照型のため、代入でコピーされることはなく、参照によって受け渡され、ヒープに割り当てられます。
  • レコードは Win32 プラットフォームで演算子のオーバーロードができますが、クラスは演算子のオーバーロードはできません。
  • レコードは、デフォルトの引数なしコンストラクタを使って自動的に作成されますが、クラスは明示的に作成する必要があります。レコードにはデフォルトの引数なしコンストラクタがあるため、ユーザー定義のレコード コンストラクタは必ず 1 つ以上のパラメータを持つ必要があります。
  • レコード型はデストラクタを持つことができません。
  • 仮想メソッド(virtualdynamic、および message キーワードによって指定)は、レコード型では使用できません。
  • クラスとは異なり、レコード型は Win32 プラットフォームでインターフェイスを実装できません。

ファイル型(Win32)

ファイル型は同じ型の要素のシーケンスで、Win32 プラットフォーム上で使用できます。標準入出力ルーチンは、定義済みの TextFile 型または Text 型を使用します。これらの型は、行に構成された文字が入ったファイルを表します。ファイル入出力の詳細は、"ファイル入出力" セクションの「標準ルーチンと入出力」を参照してください。

ファイル型を宣言するには、次の構文を使用します。

type fileTypeName = file of type

fileTypeName には任意の有効な識別子を使用します。型には固定サイズの型を指定します。ポインタ型は、明示であるかに関わらず許可されません。したがって、動的配列、長い文字列、クラス、オブジェクト、ポインタ、バリアント、他のファイル、またはこれらを含む構造型をファイル型に格納できません。

以下に例を示します。

 type
    PhoneEntry = record
      FirstName, LastName: string[20];
      PhoneNumber: string[15];
      Listed: Boolean;
    end;
    PhoneList = file of PhoneEntry;

これは名前と電話番号を記録するためのファイル型を宣言します。

file of ... 構文を変数の宣言で直接使用することもできます。以下に例を示します。

 var List1: file of PhoneEntry;

用語の file 自体は、型なしファイルを表します。

 var DataFile: file;

詳細については、「標準ルーチンと入出力」の "型なしファイル" を参照してください。

配列の要素やレコードのフィールドにファイル型を使用できません。

関連項目

コード サンプル