Edited at

<7> レコード型 (標準 Pascal 範囲内での Delphi 入門)


7. レコード型 (Records)

レコード型は他の言語では構造体とも呼ばれ、複数の型を持つ要素の集合を表します。

このレコード型をクラス型に拡張していって Object Pascal が生まれました。割愛しますが、現在の Delphi のレコード型はクラス型と同じようにメソッドを持つ事もできます。


7.1. 固定レコード

レコード型は決まった個数の要素から構成される構造型です。各要素はフィールド (欄)と呼ばれます。レコード型は配列と異なり、違った型を持つ事ができます。フィールド名 (フィールド指定子) のスコープはレコードに限定されます。

レコード型 =

[packed] record フィールドリスト end.

フィールドリスト =
フィールド宣言 ";" {フィールド宣言 ";"} [レコード可変部] [";"]

フィールド宣言 =
識別子リスト ":" 型

例えば年月日を格納するためのレコード 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 を持ちます。

このレコードを使うためには変数として宣言する必要があります。

var 

Record1, Record2: TDateRec;

もちろん、レコードを再利用しないのであれば、レコード定義と同時に変数宣言する事が可能です。レコード型は再利用する事が多いので、基本的には type で型を定義してから使います。

var

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

レコードの要素 (フィールド) にアクセスするためには、レコードの名前の後にピリオドを書き、その後にフィールド名を書きます。

  Record1.Year := 1904;

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

レコード型は同じレコード型の変数へコピーできます。

var 

Record1, Record2: TDateRec;
begin
Record1.Year := 1904;
Record1.Month := Jun;
Record1.Day := 16;

Record2 := Record1;
end;

列挙型の定数と異なり、フィールド名のスコープはレコード内に限定されるため、

type

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

TDateRec2 =
record
Year: Integer;
Month: Integer;
Day: Integer;
end;

TDateRec と TDateRec2 は同時に定義できます。もちろん同じフィールド名が同じ型である必要もありません。


7.2. 可変レコード (レコードの可変部分)

レコード型には可変部分があります。冗長なレコードの記述を簡単にする事ができます。

レコード可変部 (可変部分) = 

case [識別子 ":"] 型識別子 of 可変部 ";" {可変部 ";"}

可変部 =
定数式 {"," 定数式} ":" "(" [フィールドリスト] ")"

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

type

TEmployee =
record
FirstName, LastName: packed array [1..20] of Char;
BirthDate: TDateRec;
case Salaried: Boolean of
True: (AnnualSalary: Real);
False: (HourlyWage: Real);
end;

可変部分の Salaried はタグフィールドです。なくても構いません。タグ型は Salaried の場合 Boolean ですが、これは可変部分が AnnualSalary と HourlyWage の二つのフィールドしかないからです。もっと多くの可変部がある場合には他の順序型を用います。


RecordTest.pas

program RecordTest(output);

type
TDateRec =
record
Year: Integer;
Month: (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
Day: 1..31;
end;
TEmployee =
record
FirstName, LastName: packed array [1..10] of Char;
BirthDate: TDateRec;
case Salaried: Boolean of
True: (AnnualSalary: Real);
False: (HourlyWage: Real);
end;
var
Employee: TEmployee;
begin
Employee.FirstName := 'Blaise ';
Employee.LastName := 'Pascal ';
Employee.BirthDate.Year := 1623;
Employee.BirthDate.Month := Jun;
Employee.BirthDate.Day := 19;
Employee.Salaried := True;
Employee.AnnualSalary := 100000;

end.


上記例では Employee.SalariedTrue、つまり年棒制ですから、可変部のフィールド (可変要素) Employee.AnnualSalary に金額を代入しています。

可変部のフィールドはセミコロン区切りで複数記述する事が可能で、可変部にフィールドを記述しない事 (空の可変要素) も可能です。


RecordTest2.pas

program RecordTest2(output);

type
TDateRec =
record
Year: Integer;
Month: (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
Day: 1..31;
end;
TDetails = (Company, Individual, Student);
TCustomer =
record
FirstName, LastName: packed array [1..10] of Char;
BirthDate: TDateRec;
case CompanyDetail: TDetails of
Company:
(
CompanyName: packed array [1..20] of Char;
Employees: Integer;
);
Individual:
();
Student:
(
SchoolType: (Kindergarten, ElementarySchool, JuniorHighSchool,
HighSchool, VocationalCollege, University);
SchoolName: packed array [1..20] of Char;
Grade: Integer;
);
end;
var
Customer: TCustomer;
begin
Customer.FirstName := 'Blaise ';
Customer.LastName := 'Pascal ';
Customer.BirthDate.Year := 1623;
Customer.BirthDate.Month := Jun;
Customer.BirthDate.Day := 19;
Customer.CompanyDetail := Company;
Customer.CompanyName := 'PASCAL COMPANY ';
Customer.Employees := 10;

end.


上記例では職業形態 (CompanyDetail) によって入力フィールドを変えることができるようになっています。CompanyDetail が Company の時は会社名と従業員数が入力でき、Individual の時には入力フィールドはなく、Student の時は学校の種類と学校名と学年を入力できます。

可変部のフィールド名はレコード内でユニークである必要があります。上記例だと CompanyNameと SchoolName を同じ名前の Name という識別子にする事はできません。

可変部分はメモリの同じ領域を共有しており、コンパイラは最も大きい可変部のすべてのフィールドを格納できるだけのメモリを割り当てます。このため、ある可変部のフィールドに値を書き込んだ後、別の可変部のフィールドに値を書き込むとデータを破壊する事があります。

  ...

Customer.CompanyDetail := Company;
Customer.CompanyName := 'PASCAL ';
Customer.Employees := 10;

Customer.CompanyDetail := Student;
Customer.SchoolType := ElementarySchool;

Customer.CompanyDetail := Company;

Writeln(Customer.CompanyName);
end.

上記例では Customer.CompanyName は破壊されます。

See also:


(7.2.1.) 共用体

レコードを可変部のみで作るとどうなるでしょうか?

program UnionTest(output);

{$APPTYPE Console}
type
TWordRec =
packed record
case Integer of
0: (
LoByte: UInt8;
HiByte: UInt8;
);
1: (Value: UInt16);
end;
var
WordRec: TWordRec;
begin
WordRec.Value := $1020;
Writeln(WordRec.HiByte);
Writeln(WordRec.LoByte);
end.

他の言語で共用体と呼ばれるものができます。共用体にする場合、各可変部のフィールドの合計サイズを等しくしておく必要があります。

標準 Pascal では標準で定義された型が少ないので、共用体を使うのは難しいと思います。

See also:


(7.2.2.) レコード定数とグローバルレコード変数の初期化

Delphi では次のようなレコード定数が使えます。

type

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

// グローバルレコード定数
const
DataRec1: TDateRec = (Year: 1904; Month: Jun; Day: 16);

// グローバルレコード変数と初期化
var
DataRec2: TDateRec = (Year: 1904; Month: Jun; Day: 16);
procedure Sub;
//var
// ローカルレコード変数は初期化できない
// DataRec2: TDateRec = (Year: 1904; Month: Jun; Day: 16);
begin
...
end; { Sub }
begin
...
end;

レコード定数を宣言するには、( ) 内に、各フィールドの値を フィールド名: 値 のようにし、各フィールドをセミコロン ; で区切ります。

フィールド名の指定は定義順でなくてはなりませんが、フィールドを省略する事もできます。省略されたフィールドは 0 で初期化されるため、以下のような使い方もできます。

type

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

const
InitRec: TDateRec = (); // 初期化用レコード定数 (フィールドをすべて省略)

var
DataRec: TDateRec;
begin
DataRec.Year := 1904;
DataRec.Month := Jun;
DataRec.Day := 16;

DataRec := InitRec; // レコード定数を使った初期化

Writeln(DataRec.Year);
Writeln(Ord(DataRec.Month));
Writeln(DataRec.Day);
end.

See also:


7.3. with 文

with 文にレコード型変数を指定すると、フィールド名を変数名として使用できるようになります。

with 文 = 

with 変数 {"," 変数} do 文.

この変数名の有効範囲 (スコープ) は with 文のブロックの中だけに限定されます。


WithTest.pas

program WithTest(output);

type
TDateRec =
record
Year: Integer;
Month: (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
Day: 1..31;
end;
var
DateRec: TDateRec;
begin
{ #1 }
DateRec.Year := 1904;
DateRec.Month := Jun;
DateRec.Day := 16;

{ #2 }
with DateRec do
begin
Year := 1904;
Month := Jun;
Day := 16;
end;
end.


また、with R1, R2 ... Rn do 文 という形式は、

with R1 do

with R2 do
...
with Rn do
;

このような with 文の入れ子と同じになります。

See also:


索引

[ ← 6. 構造型の概要と配列型 ] [ ↑ 目次へ ] [ → 8. 集合型 ]