7. レコード型 (Records)
レコード型は他の言語では構造体とも呼ばれ、複数の型を持つ要素の集合を表します。
このレコード型をクラス型に拡張していって Object Pascal が生まれました。
割愛しますが、現在の Delphi のレコード型はクラス型と同じようにメソッドを持つ事もできます。
7.1. 固定レコード (Fixed Records)
レコード型は決まった個数の要素から構成される構造型です。各要素は**フィールド (欄)**と呼ばれます。レコード型は配列と異なり、違った型を持つ事ができます。フィールド名 (フィールド指定子) のスコープはレコードに限定されます。
レコード型 =
[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. 可変レコード (レコードの可変部分) (Variant Records)
レコード型には可変部分があります。冗長なレコードの記述を簡単にする事ができます。
レコード可変部 (可変部分) =
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 の二つのフィールドしかないからです。もっと多くの可変部がある場合には他の順序型を用います (通常は列挙型を使います)。
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.Salaried
が True、つまり年棒制ですから、可変部のフィールド (可変要素) Employee.AnnualSalary
に金額を代入しています。
可変部のフィールドはセミコロン区切りで複数記述する事が可能で、可変部にフィールドを記述しない事 (空の可変要素) も可能です。
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:
- レコードの可変部分 (DocWiki)
- (10.2.1.) New と Dispose の二番目の書式 - <10> ポインタ型 (Qiita)
- (10.2.1.) New と Dispose の二番目の書式 - <10> ポインタ型〔裏〕(Qiita)
(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 Statement)
with 文にレコード型変数を指定すると、フィールド名を変数名として使用できるようになります。
with 文 =
with 変数 {"," 変数} do 文.
この変数名の有効範囲 (スコープ) は with 文のブロックの中だけに限定されます。
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. 集合型 ]