12

在 Pascal 中有两种类型声明:

  • 类型别名:type NewName = OldType
  • 类型创建:type NewType = type OldType

前者只是创建方便的简写,就像C 中的typedef一样。别名彼此兼容,并且与它们的原始类型兼容。创建的类型是故意不兼容的,如果没有明确的和不安全的定义类型转换,就不能混合。

var
  nn: NewName; nt: NewType; ot: OldType;
...
  nn := ot; // should work
  nt := ot; // should break with type safety violation error.

  nt := NewType(ot); // Disabling type safety. Should work even if 
  // it has no sense semantically and types really ARE incompatible.

据我了解,这些是 Pascal 基础知识。

现在让我们看看一种特定类型及其两个别名:

  • System.Types.TStringDynArray =字符串数组;
  • System.TArray<T> = T的数组
    • 特别是这意味着TArray<string> =字符串数组根据定义。

现在让我们使用返回前一个类型别名的函数并将其结果提供给期望后一个类型的函数:

uses Classes, IOUtils;

 TStringList.Create.AddStrings(
    TDirectory.GetFiles('c:\', '*.dll') );

 TStringList.Create.AddStrings(
     TArray<string>( // this is required by compiler - but why ???
         TDirectory.GetFiles('c:\', '*.dll') ) );

由于类型冲突,第一个片段无法编译。第二个可以愉快地编译和工作,但是对于未来的类型更改很脆弱并且是多余的。

QC 告诉编译器是正确的,而 RTL 设计是错误的。 http://qc.embarcadero.com/wc/qcmain.aspx?d=106246

为什么编译器就在这里?为什么这些别名不兼容?甚至 RTL 的设计方式也表明它们被认为是兼容的!

PS。大卫提出了更简单的例子,不使用 TArray<T>

 type T1 = array of string; T2 = array of string;

 procedure TForm1.FormCreate(Sender: TObject);
  function Generator: T1;
    begin Result := T1.Create('xxx', 'yyy', 'zzz'); end;
  procedure Consumer (const data: T2);
    begin
      with TStringList.Create do 
      try
        AddStrings(data);
        Self.Caption := CommaText;
      finally
        Free;
      end;
    end;
  begin
    Consumer(Generator);
  end;

相同的陷阱没有解释......

聚苯乙烯。现在有许多文档参考。我想强调一件事:虽然这个限制可能间接继承自 1949 年的帕斯卡报告,但今天是 2012 年,德尔福使用的方式与半个世纪前的学校实验室截然不同。我列举了一些保持这种限制的坏影响,但没有看到任何好的影响。

具有讽刺意味的是,这种限制可以在不违反 Pascal 规则的情况下解除:在 Pascal 中没有像开放数组和动态数组这样的非严格野兽。所以让那些原来的固定数组随心所欲地被限制,但是开放数组和动态数组不是帕斯卡公民,没有义务被它的码本限制!

请在 QC 或什至在这里与 Emba 交流,但如果你只是路过而不表达你的意见 - 什么都不会改变!

4

3 回答 3

11

理解这个问题的关键是语言指南中的类型兼容性和身份主题。我建议您好好阅读该主题。

简化示例也很有帮助。示例中包含泛型主要是为了使事情复杂化和混淆。

program TypeCompatibilityAndIdentity;
{$APPTYPE CONSOLE}

type
  TInteger1 = Integer;
  TInteger2 = Integer;
  TArray1 = array of Integer;
  TArray2 = array of Integer;
  TArray3 = TArray1;

var
  Integer1: TInteger1;
  Integer2: TInteger2;
  Array1: TArray1;
  Array2: TArray2;
  Array3: TArray3;

begin
  Integer1 := Integer2; // no error here
  Array1 := Array2; // E2010 Incompatible types: 'TArray1' and 'TArray2'
  Array1 := Array3; // no error here
end.

从文档中:

当一个类型标识符使用另一个类型标识符声明时,没有限定,它们表示相同的类型。

这意味着TInteger1TInteger2同一类型,并且确实与 是同一类型Integer

文档中的进一步说明是:

用作类型名称的语言结构在每次出现时都表示不同的类型。

的声明TArray1TArray2属于这一类。这意味着这两个标识符表示不同的类型。

现在我们需要查看讨论兼容性的部分。这给出了一组规则来确定两种类型是否兼容或分配兼容。事实上,我们可以通过参考另一个帮助主题来简化讨论:Structured Types, Array Types and Assignments,它清楚地说明了:

只有当它们是相同类型时,数组才是赋值兼容的。

这清楚地说明了为什么分配Array1 := Array2会导致编译器错误。

您的代码查看了传递参数,但我的代码专注于分配。问题是相同的,因为正如调用过程和函数帮助主题所解释的:

调用例程时,请记住:

  • 用于传递类型化 const 和 value 参数的表达式必须与相应的形式参数赋值兼容。
  • …………
于 2012-06-14T14:19:34.737 回答
7

Delphi 是一种强类型语言。这意味着相同(在这种情况下,我的意思是它们的定义看起来完全相同)类型不兼容赋值。

当您编写时,array of <type>您定义的是类型而不是别名。正如大卫在他的评论中已经说过的那样,两种相同的类型

type 
  T1 = array of string; 
  T2 = array of string;

不兼容分配。

同样适用

type
  TStringDynArray = array of string;
  TArray<T> = array of string;

人们经常忘记相同类型的不兼容,我猜他们在引入 IOUtils 时就会这样做。从理论上讲,TStringDynArray 的定义应该已更改为,TStringDynArray = TArray<string>但我想这可能会引发其他问题(不是说泛型的错误......)。

于 2012-06-14T12:14:17.040 回答
2

Delphi 也有同样的问题,我想将值从一个相同的数组传递到另一个数组。我不仅有两个类似数组分配的“不兼容”问题,而且我也无法使用“Copy()”过程。为了解决这个问题,我发现我可以使用指向字符串数组类型数组的指针。

例如:

type RecArry = array of array of string
     end;
var TArryPtr : ^RecArry;

现在,我可以将任何固定数组中的值传递给另一个相同的数组,而不会出现任何兼容性或功能问题。例如:

TArryPtr := @RecArry.LstArray //This works!
TArryPtr := @LstArray         //This also works!

使用这个创建的数组分配模板,我现在可以毫无问题地处理所有二维数组。但是,应该理解的是,当访问这种类型的字符串数组指针时,会创建一个额外的元素,这样当我们期望这种类型的数组时,我们会期望下面的二维数组,例如:

Two_Dimensional_Fixed_Array[10][0]

我们现在得到一个额外的元素调整数组,如下所示:

New_Two_Dimensional_Fixed_Array[10][1]    

这意味着我们必须使用一些稍微棘手的代码来访问指针数组,因为 Two_Dimensional_Fixed_Array[10][0] 中的所有填充元素都已向下移动,因此它们偏移 1,如 New_Two_Dimensional_Fixed_Array[10][1 ]。

因此,我们通常会在 Two_Dimensional_Fixed_Array [1][0]中找到值'X' ,现在可以在 TArryPtr [0][1]中找到它。

这是我们所有人都必须忍受的权衡!

要记住的另一个重要注意事项是声明指针数组时的定义。当指针数组被声明类型时,Borland 编译器将不允许指针数组具有与其所指向的数组相同的元素大小。例如,如果一个数组被声明为:

Orig_Arry : array [1..50,1] of string;

应该指向它的指针数组将以下列方式声明:

Type Pntr_Arry : array [1..50,2] of string;

你注意到额外的元素了吗?我猜 Borland 编译器必须扩大数组指针以允许指针地址。

于 2013-02-18T10:35:08.973 回答