为什么两个别名的“字符串数组”处理不同?

时间:2012-06-14 08:28:50

标签: delphi generics

在Pascal中有两种类型声明:

  • 类型别名:类型NewName = OldType
  • 类型创建:类型NewType = 类型 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&lt; T&gt;

 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;

相同的问题没有解释......

PPS。现在有很多doc refs。我想强调一件事:虽然这种限制可能间接地继承自1949年的Pascal报告,但今天是2012年,德尔福与半个世纪前的学校实验室的使用方式截然不同。 我点了几个BAD效果来保持这个限制,但没有看到任何好的。

讽刺的是,这种限制可能会在不破坏Pascal规则的情况下被解除:在Pascal中没有像Open Arrays和Dynamic Arrays这样的非严格的野兽。所以让那些原始的固定数组按照他们的意愿限制,但Open Arrays和Dynamic Arrays不是Pascal公民,也没有义务受其代码簿的限制!

请在QC或甚至在这里与Emba联系,但如果你只是在没有表达意见的情况下过去 - 没有什么会改变的!

3 个答案:

答案 0 :(得分:11)

理解此问题的关键是语言指南中的Type Compatibility and Identity主题。我建议你对这个话题有一个很好的解读。

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

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导致编译器错误的原因。

您的代码查看了传递参数,但我的重点是分配。问题是相同的,因为Calling Procedures and Functions帮助主题解释了:

  

调用例程时,请记住:

     
      
  • 用于传递类型const和值参数的表达式必须与相应的形式参数分配兼容。
  •   
  • .......
  •   

答案 1 :(得分: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>,但我想这可能会引发其他问题(不是说泛型错误......)。

答案 2 :(得分:2)

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

例如:

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

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

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

使用这个创建的数组赋值模板,我现在可以使用所有二维数组而不会出现任何问题。但是,应该理解,当访问这种类型的字符串数组指针时,会创建一个额外的元素,这样当我们期望下面这种类型的数组2D数组时,例如:

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编译器必须扩展数组指针以允许指针地址。