可以在FreePascal中的变体记录的案例中复制标识符吗?

时间:2016-04-21 06:24:34

标签: record pascal freepascal variant

这是我的问题:我想创建一个记录类型,其中在变体记录的情况下,一些(但不是全部)将具有某个字段。根据{{​​3}},这是完全合法的。然而,当我尝试编译以下代码时:

program example;

{$mode objfpc}{$H+}

uses sysutils;

type
  maritalStates = (single, married, widowed, divorced);

  TPerson = record
    name: record
      first, middle, last: string;
    end;
    sex: (male, female);
    dob: TDateTime;
    case maritalStatus: maritalStates of
      single: ( );
      married, widowed: (marriageDate: TDateTime);
      divorced: (marriageDate, divorceDate: TDateTime;
        isFirstDivorce: boolean)      
  end;

var
  ExPerson: TPerson;

begin
ExPerson.name.first := 'John';
ExPerson.name.middle := 'Bob';
ExPerson.name.last := 'Smith';
ExPerson.sex := male;
ExPerson.dob := StrToDate('05/05/1990');
ExPerson.maritalStatus := married;
ExPerson.marriageDate := StrToDate('04/01/2015');

end.

编译失败,出现以下错误:

$ fpc ex.pas
Free Pascal Compiler version 3.0.0 [2016/02/14] for x86_64
Copyright (c) 1993-2015 by Florian Klaempfl and others
Target OS: Win64 for x64
Compiling ex.pas
ex.pas(19,18) Error: Duplicate identifier "marriageDate"
ex.pas(21,3) Error: Duplicate identifier "marriageDate"
ex.pas(35,4) Fatal: There were 2 errors compiling module, stopping
Fatal: Compilation aborted
Error: C:\lazarus\fpc\3.0.0\bin\x86_64-win64\ppcx64.exe returned an error exitcode

维基是完全错了,还是我错过了什么?有没有办法达到我想要的效果?

3 个答案:

答案 0 :(得分:3)

非常有趣的问题。我确信这是可能的。如果您将代码修改为:

..
married, widowed, divorced: (marriageDate: TDateTime);
divorced: (divorceDate: TDateTime; isFirstDivorce: boolean)
..

它有效,但它不是你想要的结果。由于婚姻日期和离婚日期相互叠加(如评论中所述!)

enter image description here

此图片取自“Pascal用户手册(第4版)”,您可以看到变体部分具有相同的存储位置。

根据Pascal users manual (4th edition)和“Turbo Pascal ISBN 3-89011-060-6”一书,所引用的wiki上的记录声明无效

  
      
  1. 所有字段名称必须不同 - 即使它们出现在不同的变体中。
  2.   
  3. 如果变量为空(即没有字段),则表单为:   C:()
  4.   
  5. 字段列表只能包含一个变体部分,并且必须遵循记录的固定部分。
  6.   
  7. 变体本身可能包含变体部分;因此,变体部分可以嵌套。
  8.   
  9. 记录类型中引入的枚举类型常量标识符的范围扩展到封闭块。
  10.   

第1点是相关的!书“Turbo Pascal”建议的解决方案是为多次出现的字段名称使用唯一的前缀。

在你的情况下你可以看起来像:

TPerson = record
    name: record
      first, middle, last: string;
    end;
    sex: (male, female);
    dob: TDateTime;
    case maritalStatus: maritalStates of
      single: ( );
      married, widowed: (marMarriageDate: TDateTime);
      divorced: (divMarriageDate, divorceDate: TDateTime;
        isFirstDivorce: boolean)      
  end;

另一个解决方案是将已婚,被驱逐......定义为记录类型。

..
married       : (m: TMarried);
divorced      : (d: TDivorced);
..

答案 1 :(得分:1)

这似乎有效

program example;

{$mode objfpc}{$H+}

uses sysutils;

type
  TMarried          = record
                        marriageDate  : TDateTime
                      end;

  TDivorced         = record
                        marriageDate  : TDateTime;
                        divorceDate   : TDateTime;
                        isFirstDivorce: boolean
                      end;

  TWidowed          = TMarried;

  maritalStates = (single, married, widowed, divorced);

  TPerson = record
    name: record
      first, middle, last: string;
    end;
    sex: (male, female);
    dob: TDateTime;
    case maritalStatus: maritalStates of
      single        : ();
      married       : (m: TMarried);
      widowed       : (w: TWidowed);
      divorced      : (d: TDivorced);
  end;

var ExPerson: TPerson;

begin
  with ExPerson do
  begin
    name.first := 'John';
    name.middle := 'Bob';
    name.last := 'Smith';
    sex := male;
    dob := StrToDate('05/05/1990');
    maritalStatus := married;
    m.marriageDate := StrToDate('04/01/2015');
  end;
end.

编辑:您也可以内联定义记录,但我认为上述内容更清晰。这是另一种方式:

program example;

{$mode objfpc}{$H+}

uses sysutils;

type
  maritalStates = (single, married, widowed, divorced);

  TPerson = record
    name: record
      first, middle, last: string;
    end;
    sex: (male, female);
    dob: TDateTime;
    case maritalStatus: maritalStates of
      single   : ();
      married  : (m: record marriageDate: TDateTime end);
      widowed  : (w: record marriageDate: TDateTime end);
      divorced : (d: record
                       marriageDate  : TDateTime;
                       divorceDate   : TDateTime;
                       isFirstDivorce: boolean
                     end)
  end;

var ExPerson: TPerson;

begin
  with ExPerson do
  begin
    name.first  := 'John';
    name.middle := 'Bob';
    name.last   := 'Smith';
    sex := male;
    dob := StrToDate('05/05/1990');
    maritalStatus  := married;
    m.marriageDate := StrToDate('04/01/2015');
  end;
end.

答案 2 :(得分:0)

Baltasar建议编译,但不能做你想要的。 marriageDatedivorceDate会重叠,写入其中一个也会修改另一个,因为它们只是在同一个地址。

但在这种情况下,根本没有充分的理由可以使用变体记录。

为什么不简单:

type
  maritalStates = (single, married, widowed, divorced);

  TPerson = record
    name: record
      first, 
      middle, 
      last: string;
    end;
    sex: (male, female);
    dob: TDateTime;
    maritalStatus: maritalStates; // single, married, widowed, divorced
    marriageDate: TDateTime;      // married, widowed, divorced   
    divorceDate   : TDateTime;    // divorced
    isFirstDivorce: boolean;      // divorced
  end;

使用和布局正是您所需要的。如果某个字段不适用(例如marriageDatesingledivorceDatemarried),则您根本不使用该字段。

这与变体记录相同。在那里,您还只设置了适用的字段。请注意,编译器或运行时不会阻止您写入变体记录的错误字段,即在变体记录中,如果状态为single,您仍然可以写入或读取{{1}即使这没有任何意义。

如果要区分几种不同的设置,只需在注释中执行此操作,并忘记变体记录,此处不需要它。现在你可以做到:

divorceDate

更新

只是为了表明完全没有必要制作这种记录变体,

我将发布我的Project62.dpr,它显示了相应字段和相同记录大小的完全相同的偏移量:

var
  P: TPerson;
begin
  P.name.first := 'Bob';
  P.name.middle := 'The';
  P.name.last := 'Builder';
  P.sex := male;
  P.dob := StrToDate('05/05/1980');
  P.maritalStatus := divorced;
  P.marriageDate := StrToDate('04/01/2013');
  P.divorceDate := StrToDate('04/02/2016');
  P.isFirstDivorce := True;

  // etc...

输出(在Windows上):

program Project62;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  maritalStates = (single, married, widowed, divorced);
  tsex = (male, female);

  // No variant part
  PPerson = ^TPerson;
  TPerson = record
    name: record
      first,
      middle,
      last: string;
    end;
    sex: tsex;
    dob: TDateTime;
    maritalStatus: maritalStates; // single, married, widowed, divorced
    marriageDate: TDateTime;      // married, widowed, divorced
    divorceDate   : TDateTime;    // divorced
    isFirstDivorceDate: boolean;      // divorced
  end;

  // Variant part like tonypdmtr's record
  PPerson2 = ^TPerson2;
  TPerson2 = record
    name: record
      first,
      middle,
      last: string;
    end;
    sex: tsex;
    dob: TDateTime;
    case maritalStatus: maritalStates of
      single:   ();
      widowed:  (w: record marriageDate: TDateTime; end); // overlaps with m.marriageDate and d.marriageDate
      married:  (m: record marriageDate: TDateTime; end); // overlaps with w.marriageDate and d.marriageDate
      divorced: (d: record
                      marriageDate: TDateTime;            // overlaps with w.marriageDate and m.marriageDate
                      divorceDate: TDateTime;             // same offset as in my non-variant version
                      isFirstDivorceDate: Boolean         // same offset as in my non-variant version
                    end);
  end;

begin
  try
    Writeln('TPerson:  size = ', Sizeof(TPerson));
    Writeln('TPerson.maritalStatus:         offset = ', NativeUInt(@PPerson(nil)^.maritalStatus));
    Writeln('TPerson.marriageDate:          offset = ', NativeUInt(@PPerson(nil)^.marriageDate));
    Writeln('TPerson.divorceDate:           offset = ', NativeUInt(@PPerson(nil)^.divorceDate));
    Writeln('TPerson.isFirstDivorceDate:    offset = ', NativeUInt(@PPerson(nil)^.isFirstDivorceDate));
    Writeln;
    Writeln('TPerson2:  size = ', Sizeof(TPerson2));
    Writeln('TPerson2.maritalStatus:        offset = ', NativeUInt(@PPerson2(nil)^.maritalStatus));
    Writeln('TPerson2.w.marriageDate:       offset = ', NativeUInt(@PPerson2(nil)^.w.marriageDate));
    Writeln('TPerson2.m.marriageDate:       offset = ', NativeUInt(@PPerson2(nil)^.m.marriageDate));
    Writeln('TPerson2.d.marriageDate:       offset = ', NativeUInt(@PPerson2(nil)^.d.marriageDate));
    Writeln('TPerson2.d.divorceDate:        offset = ', NativeUInt(@PPerson2(nil)^.d.divorceDate));
    Writeln('TPerson2.d.isFirstDivorceDate: offset = ', NativeUInt(@PPerson2(nil)^.d.isFirstDivorceDate));
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

32位布局可以放在一个简单的图中:

TPerson:  size = 56
TPerson.maritalStatus:         offset = 24
TPerson.marriageDate:          offset = 32
TPerson.divorceDate:           offset = 40
TPerson.isFirstDivorceDate:    offset = 48

TPerson2:  size = 56
TPerson2.maritalStatus:        offset = 24
TPerson2.w.marriageDate:       offset = 32
TPerson2.m.marriageDate:       offset = 32
TPerson2.d.marriageDate:       offset = 32
TPerson2.d.divorceDate:        offset = 40
TPerson2.d.isFirstDivorceDate: offset = 48