LoginSignup
6
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2019-03-11

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 の二つのフィールドしかないからです。もっと多くの可変部がある場合には他の順序型を用います (通常は列挙型を使います)。

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 Statement)

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. 集合型 ] :sushi:

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1