标识符可以在 FreePascal 中的变体记录案例中重复吗?
Can Identifiers be duplicated across cases of a variant record in FreePascal?
这是我的问题:我想创建一个记录类型,其中在变体记录的情况下,一些(但不是全部)将具有特定字段。根据 wiki,这是完全合法的。然而,当我尝试编译以下代码时:
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.0.0\bin\x86_64-win64\ppcx64.exe returned an error exitcode
是 wiki 完全错误,还是我遗漏了什么?有什么办法可以达到我想要的效果吗?
非常有趣的问题。我确信这是可能的。如果您将代码修改为:
..
married, widowed, divorced: (marriageDate: TDateTime);
divorced: (divorceDate: TDateTime; isFirstDivorce: boolean)
..
它有效,但不是您想要的结果。由于 marriageDate 和 divorceDate 相互重叠(如评论中所述!)
此图片来自 "Pascal users manual (4th edition)",如您所见,变体部件具有相同的内存位置。
根据 Pascal users manual (4th edition) and to the book "Turbo Pascal ISBN 3-89011-060-6 the described record declaration on your quoted wiki 无效!
- All field names must be distinct - even if they occur in different variants.
- If a variant is empty (i.e., has no fields), the form is:
C:()
- A field list can have only one variant part and it must follow the fixed part of the record.
- A variant may itself contain a variant part; hence variant parts can be nested.
- The scope of enumerated type constant identifiers that are introduced in a record type extends over the enclosing block.
第 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, devorced ... 定义为记录类型。
..
married : (m: TMarried);
divorced : (d: TDivorced);
..
这似乎有效
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.
Baltasar 建议的内容可以编译,但不会执行您想要的操作。 marriageDate
和 divorceDate
会重叠,写入其中一个也会修改另一个,因为它们只是在同一个地址。
但在这种情况下,根本没有充分的理由进行变体记录。
为什么不简单:
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;
用法和布局正是您所需要的。如果某个字段不适用(例如 marriageDate
对应 single
,或 divorceDate
对应 married
),您就不要使用它。
这与变体记录相同。在那里您也只设置适用的字段。请注意,编译器或运行时不会阻止您写入变体记录的错误字段,即在变体记录中,如果状态为 single
,您仍然可以写入或读取 divorceDate
,即使那毫无意义。
如果你想区分几种不同的设置,只需在评论中进行即可,忘记变体记录,这里不需要。现在您可以:
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...
更新
只是为了表明完全没有必要制作此记录变体,
我将 post 我的 Project62.dpr,它显示了相应字段的完全相同的偏移量和相同的记录大小:
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.
输出(Windows):
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
32位的布局可以简单的画成这样:
00 TPerson: [name.first] TPerson2: [name.first]
04 [name.middle] [name.middle]
08 [name.last] [name.last]
12 [sex] [sex]
16 [dob] [dob]
24 [maritalStatus] [maritalStatus]
32 [marriageDate] [w.marriageDate] [m.marriageDate] [d.marriageDate]
40 [divorceDate] [d.divorceDate]
48 [isFirstDivorceDate] [d.isFirstDivorceDate]
这是我的问题:我想创建一个记录类型,其中在变体记录的情况下,一些(但不是全部)将具有特定字段。根据 wiki,这是完全合法的。然而,当我尝试编译以下代码时:
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.0.0\bin\x86_64-win64\ppcx64.exe returned an error exitcode
是 wiki 完全错误,还是我遗漏了什么?有什么办法可以达到我想要的效果吗?
非常有趣的问题。我确信这是可能的。如果您将代码修改为:
..
married, widowed, divorced: (marriageDate: TDateTime);
divorced: (divorceDate: TDateTime; isFirstDivorce: boolean)
..
它有效,但不是您想要的结果。由于 marriageDate 和 divorceDate 相互重叠(如评论中所述!)
此图片来自 "Pascal users manual (4th edition)",如您所见,变体部件具有相同的内存位置。
根据 Pascal users manual (4th edition) and to the book "Turbo Pascal ISBN 3-89011-060-6 the described record declaration on your quoted wiki 无效!
- All field names must be distinct - even if they occur in different variants.
- If a variant is empty (i.e., has no fields), the form is: C:()
- A field list can have only one variant part and it must follow the fixed part of the record.
- A variant may itself contain a variant part; hence variant parts can be nested.
- The scope of enumerated type constant identifiers that are introduced in a record type extends over the enclosing block.
第 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, devorced ... 定义为记录类型。
..
married : (m: TMarried);
divorced : (d: TDivorced);
..
这似乎有效
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.
Baltasar 建议的内容可以编译,但不会执行您想要的操作。 marriageDate
和 divorceDate
会重叠,写入其中一个也会修改另一个,因为它们只是在同一个地址。
但在这种情况下,根本没有充分的理由进行变体记录。
为什么不简单:
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;
用法和布局正是您所需要的。如果某个字段不适用(例如 marriageDate
对应 single
,或 divorceDate
对应 married
),您就不要使用它。
这与变体记录相同。在那里您也只设置适用的字段。请注意,编译器或运行时不会阻止您写入变体记录的错误字段,即在变体记录中,如果状态为 single
,您仍然可以写入或读取 divorceDate
,即使那毫无意义。
如果你想区分几种不同的设置,只需在评论中进行即可,忘记变体记录,这里不需要。现在您可以:
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...
更新
只是为了表明完全没有必要制作此记录变体,
我将 post 我的 Project62.dpr,它显示了相应字段的完全相同的偏移量和相同的记录大小:
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.
输出(Windows):
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
32位的布局可以简单的画成这样:
00 TPerson: [name.first] TPerson2: [name.first]
04 [name.middle] [name.middle]
08 [name.last] [name.last]
12 [sex] [sex]
16 [dob] [dob]
24 [maritalStatus] [maritalStatus]
32 [marriageDate] [w.marriageDate] [m.marriageDate] [d.marriageDate]
40 [divorceDate] [d.divorceDate]
48 [isFirstDivorceDate] [d.isFirstDivorceDate]