【问题标题】:why two aliases to "array of string" treated differently?为什么“字符串数组”的两个别名处理不同?
【发布时间】:2012-06-17 06:21:48
【问题描述】:

在 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;
    • 特别是指 TArray = array of 字符串; 根据定义。

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

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') ) );

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

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

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

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

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

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

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

【问题讨论】:

  • 无需将泛型引入其中。尝试type T1 = array of string; T2 = array of string,然后尝试将 T1 类型的变量分配给 T2 类型之一。

标签: delphi generics


【解决方案1】:

理解这个问题的关键是语言指南中的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 和值参数的表达式必须与相应的形式参数赋值兼容。
  • .......

【讨论】:

  • 每种类型都与自身兼容。如果两个不同的类型至少满足以下条件之一,则它们是兼容的。 - 以及很长的特殊情况列表。它基本上是什么意思?香草学校 Pascal 或早期受限 Turbo 编译器的限制现在阻碍了实际程序的可组合性。是的,这些类型在形式上是不同的,但它们实际上是兼容的。该示例声称创建了两种不同的类型,TS1 和 TS2。 但它声称它们是兼容的。它是什么 ?这是对形式结构的实际否定。数组也应该兼容!
  • 不,我不太关心 firect var1 := var2。它几乎没有实际意义(大多数时候)。您可能需要var1 := expression-with-var2,但什么是带有记录和数组的表达式?它们是某种功能。 MapReduce 等。所以我关心链接功能的易用性,关于简洁的表达源。变量之间的分配对我没有多大帮助,但是当我需要添加很多样板来组合两个标准函数时 - 它有点隐藏在跑道上!
  • 将参数传递给过程非常是的,就像在 Delphi 中编程和在汇编器中编程一样。所有图灵完备的语言只是彼此的语法糖。但实际上我并没有太多使用直接的非变异分配。只是没有什么实际意义。但我确实将一个函数称为另一个函数的结果。如果编译器将其视为assignin - 不管怎样,这是它的内部选择。我关心的是兼容性 = 表达式和函数的可组合性。
  • 只有 Emba 可以改变它们。 我们可以提出这个问题。我们可以问‘你好!德尔福已经过时了!输入继续!事实上,Emba 在其 RTL 中犯了一个错误,这正是提高声音的好时机。就像微软网站中的反 MSIE cmets 一样,这个案例突出表明,这种遗留限制失去了任何实际意义,只会妨碍生产力。 Emba RTL 架构师——最有见识和最有经验的架构师——未能考虑到这一限制。如果这不能表明这个限制是多么愚蠢,那我不知道可以是什么。
  • 是的,我昨天浏览了 1949/1974 年的“帕斯卡报告”,该规则很适合那里。没有像“开放数组”这样的东西,你不能做procedure xxx(list yy: array of byte);一切都必须有名字。而且你只对像冒泡排序这样的小sn-ps用于你的学校作业,没有其他的代码,现在是你昨天的代码,所以是的,你可以计划AL的数据类型。 1949年你可以。但今天只是不一致。
【解决方案2】:

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

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

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

分配不兼容。

同样适用

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

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

【讨论】:

  • Oookay,所以你这样做了TStringDynArray = TArray&lt;T&gt; - 为什么他们突然变得兼容分配?如果“相同类型与赋值不兼容”,则此定义不应更改任何内容。为什么我们有Windows.DWORD = System.Types.DWORD;System.Types.DWORD = System.LongWord; 并期望它们兼容? type X = type Y 左结构的意义是什么? “其他问题”是指与为先前版本的 Delphi 制作的库的向后兼容性?像 TStrings 扩展没有考虑 IOUtils 或反之亦然?不需要的类型异化做到了!
  • @Arioch: type X = type Y 为 X 创建一个新的 RTTI。因此您可以确定您是否有一个带有“= type string”的 TMyString 或 TYourString。简单类型是赋值兼容的。但是数组是一个复杂的(“构造的”)类型,就像记录一样。
  • 这在 TurboPascal 中可能有意义,其中 (1) 所有数组都有固定长度,(2) 你只能从函数返回简单类型。据我所知,根本无法分配构造类型,编译器不知道该怎么做。因此,您必须使用 var 参数和唯一类型别名。但是现在没有技术意义,编译器现在没有那么有限。而这种不必要的限制严重损害了程序组合的灵活性。这是古代编译器的遗产,而今天,这个遗产没有任何好处!
  • 让我们从另一个角度来看这个问题。如果 TArray 与所有继承的代码不兼容,它存在的原因是什么?也许德尔福会更好地定义type array of T = TArray&lt;T&gt;?现在,一旦您使用 TArray 它就会变成孤立的,与所有先前的代码不兼容。那么该类型的价值是什么?使用旧的好的动态数组就完成了。
  • 如果你定义了两种不同的类型,即使它们看起来一样,你这样做也是有原因的。 - 这正是提到的type TFileName = type string 构造。当我使用简单的type 时 - 我没有定义不同的类型。德尔福做到了,让我别无选择。 如果您希望它们兼容,请使用一种类型而不是 2 种 甚至 Embarcadero 本身也未能做到这一点。你如何想象来自不同地区和不同年代的所有社区会如何做到这一点?快点!对于编译器版本,我们仍然没有一个带有 IfDef 的 True Include。每个图书馆都有并维护它自己的。这么简单的事情!
【解决方案3】:

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

例如:

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

【讨论】:

    猜你喜欢
    • 2021-11-01
    • 2015-04-10
    • 2011-02-14
    • 1970-01-01
    • 2018-08-30
    • 2013-09-12
    • 2019-11-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多