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

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

このレコード型をクラス型に拡張していって **Object Pascal** が生まれました。

 - [レコード型（従来型）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E3.83.AC.E3.82.B3.E3.83.BC.E3.83.89.E5.9E.8B.EF.BC.88.E5.BE.93.E6.9D.A5.E5.9E.8B.EF.BC.89)
 - [オブジェクト型 (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#.E3.82.AA.E3.83.96.E3.82.B8.E3.82.A7.E3.82.AF.E3.83.88.E5.9E.8B)
 - [クラス型 (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#.E3.82.AF.E3.83.A9.E3.82.B9.E5.9E.8B)

割愛しますが、現在の Delphi のレコード型はクラス型と同じようにメソッドを持つ事もできます。

 - [レコード型（高度） (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E3.83.AC.E3.82.B3.E3.83.BC.E3.83.89.E5.9E.8B.EF.BC.88.E9.AB.98.E5.BA.A6.EF.BC.89)
 - [Pascal へのオブジェクト指向拡張の歴史と Delphi (Qiita)](https://qiita.com/ht_deko/items/ff2d90c9174290583377)

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

```
レコード型 =
  [packed] record フィールドリスト end.

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

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

例えば年月日を格納するためのレコード TDateRec は次のように定義できます。

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

TDateRec はフィールドリストとして年を表す[整数型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#23-integer-%E5%9E%8B)のフィールド Year、月を表す[列挙型](https://qiita.com/ht_deko/items/335e59857cc78aea951a#51-%E5%88%97%E6%8C%99%E5%9E%8B) のフィールド Month、日を表す[部分範囲型](https://qiita.com/ht_deko/items/335e59857cc78aea951a#52-%E9%83%A8%E5%88%86%E7%AF%84%E5%9B%B2%E5%9E%8B) のフィールド Day を持ちます。

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

```pascal
var 
  Record1, Record2: TDateRec;
```

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

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

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

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

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

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

  Record2 := Record1;
end;
```

[列挙型](https://qiita.com/ht_deko/items/335e59857cc78aea951a#51-%E5%88%97%E6%8C%99%E5%9E%8B)の定数と異なり、フィールド名のスコープはレコード内に限定されるため、

```pascal
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 文](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4#452-case-%E6%96%87)に似ています。可変部分はレコード宣言で他のフィールドよりも後に記述する必要があります。また、可変部分は入れ子にする事が可能です。

```pascal
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 の二つのフィールドしかないからです。もっと多くの可変部がある場合には他の[順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E5%9E%8B-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B)を用います (通常は[列挙型](https://qiita.com/ht_deko/items/335e59857cc78aea951a#51-%E5%88%97%E6%8C%99%E5%9E%8B-enumerated-types)を使います)。

```pascal: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.Salaried` が **True**、つまり年棒制ですから、可変部のフィールド (可変要素)  `Employee.AnnualSalary` に金額を代入しています。

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

```pascal: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 という識別子にする事はできません。

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

```pascal
  ...

  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)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E3.83.AC.E3.82.B3.E3.83.BC.E3.83.89.E5.9E.8B.EF.BC.88.E5.BE.93.E6.9D.A5.E5.9E.8B.EF.BC.89)
 - [(10.2.1.) New と Dispose の二番目の書式 - <10> ポインタ型 (Qiita)](https://qiita.com/ht_deko/items/745ad4a792d277d33831#1021-new-%E3%81%A8-dispose-%E3%81%AE%E4%BA%8C%E7%95%AA%E7%9B%AE%E3%81%AE%E6%9B%B8%E5%BC%8F)
 - [(10.2.1.) New と Dispose の二番目の書式 - <10> ポインタ型〔裏〕(Qiita)](https://qiita.com/ht_deko/private/3be4294e82653fb234c4#1021-new-%E3%81%A8-dispose-%E3%81%AE%E4%BA%8C%E7%95%AA%E7%9B%AE%E3%81%AE%E6%9B%B8%E5%BC%8F)

### (7.2.1.) 共用体
レコードを可変部のみで作るとどうなるでしょうか？

```pascal
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:**

 - [共用体 (Wikipedia)](https://ja.wikipedia.org/wiki/%E5%85%B1%E7%94%A8%E4%BD%93)

### (7.2.2.) レコード定数とグローバルレコード変数の初期化
Delphi では次のようなレコード定数が使えます。

```pascal
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 で初期化されるため、以下のような使い方もできます。

```pascal
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:**

 - [レコード定数 (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#.E3.83.AC.E3.82.B3.E3.83.BC.E3.83.89.E5.AE.9A.E6.95.B0)

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

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

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

```pascal: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 文` という形式は、

```pascal
with R1 do
  with R2 do
    ...
      with Rn do 
        文;
```

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

**See also:**

 - [with 文 (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#with_.E6.96.87)

# 索引
[ [← 6. 構造型の概要と配列型](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac) ] [ [↑ 目次へ](https://qiita.com/ht_deko/items/e2796788c65c2cda1de5) ] [ [→ 8. 集合型](https://qiita.com/ht_deko/items/50bac0de384d66208944) ] [:sushi:](https://qiita.com/ht_deko/items/715620809518de763a67)