【问题标题】:Why can't I return arbitrary array of string?为什么我不能返回任意字符串数组?
【发布时间】:2013-04-02 03:52:58
【问题描述】:

编译器允许我执行以下操作:

procedure MyProc(const ADynData: array of string);

procedure MyProc(const ADynData: TStringDynArray);

并像这样传递任意数据:

MyProc(['Data1', 'Data2']);

但是,不允许

function MyFunc: TStringDynArray;
....
function MyFunc: TStringDynArray;
begin
    Result := ['Data1', 'Data2'];
end;

function MyFunc: TStringDynArray;
const
    CDynData: array[0..1] of string = ('Data1', 'Data2');
begin
    Result := CDynData;
end;

这是为什么?这技术上难道不是一回事吗?

对于这些特定场景,什么是推荐的(也是最有效的)返回任意字符串数组的方法?

【问题讨论】:

  • 在 Delphi 中习惯使用 var-array-of 参数而不是尝试返回动态数组。请记住,Delphi 没有 C++ 所具有的大量精巧的值和引用语义集,这对您可以从 Delphi 中的函数返回的内容施加了一些限制。特别是,Delphi 对动态数组的内存管理由 var 数组(返回)更好地处理。如果您使用“var”而不是 const,则上面的示例将具有更直接的可比性,因为返回数组和传入一个数组在内存管理中是两件非常不同的事情。
  • @WarrenP 不这样。实际上,返回动态数组是惯用的。这允许被调用者决定数组应该有多长。当前的习惯用法是返回TArray<T>。尝试设置开放数组的长度!
  • 好的,在支持泛型的现代 Delphi 版本中,David 是对的。然而,经典的 Delphi 顽固分子仍然不依赖通用代码,因为我们不信任它。因此,我们使用 var 参数,让经典技术继续像往常一样运行。下次我们在 QualityCentral 上看到一个可怕的破坏,当编译器错误被击中时,整个事情都会可怕地死去......我们会尽量不要幸灾乐祸。 :-)
  • @WarrenP 我再问一次,被调用者确定长度的时候怎么办?即使在 Delphi 的泛型前版本中,答案也是相同的。
  • 好点。避免使用旧模式是有正当理由的。

标签: arrays delphi delphi-xe3


【解决方案1】:

不,这不是一回事。在

procedure MyProc(const ADynData: array of string);

参数是open array parameter,它与“普通”动态数组不同。 [..] 语法只能用于在函数的开放数组参数中创建开放数组。 (否则,[..] 用于在代码中指定 sets,例如 Font.Style := [fsBold, fsItalic]。但sets 只能将序数类型作为其“基本类型”,所以仍然没有这样的东西作为“字符串集”。)

换句话说,不可能像您在第二个代码 sn-p 中尝试的那样在代码中编写动态数组,

function MyFunc: TStringDynArray;
begin
  result := ['Data1', 'Data2']; // Won't work.
end;

但是,在新版本的 Delphi 中,几乎是可能的:

type
  TStringDynArray = array of string;

function MyFunc: TStringDynArray;
begin
  result := TStringDynArray.Create('A', 'B');
end;

最后,

function MyFunc: TStringDynArray;
const
  CDynData: array[0..1] of string = ('Data1', 'Data2');
begin
  result := CDynData;
end;

不起作用,因为TStringDynArraydynamic array,而CDynDatastatic array,这是两种不同的基本类型。

【讨论】:

  • 我也可以使用procedure MyProc(const ADynData: TStringDynArray); 并传递任意数组。所以基本上你不能有开放数组返回值?
  • @James:是的。开放数组仅用于参数。
  • 嗯似乎有点限制,除非背后有技术原因为什么他们不允许这样做。你认为如果每次都创建TStringDynArray 会对性能产生影响吗?是否值得缓存结果(如果它不会改变)。
  • @James:值得吗...:常识和测试会回答这个问题。
  • 是的,我明白了......并且会做到的。我的意思是您是否对创建TStringDynArray 是否是一项昂贵的操作有意见。公平地说,常识确实会说“缓存所有内容”,但我们知道这并不总是必要的。
【解决方案2】:

这个结构

['string1', 'string2'] 

被称为open array constructor。来自documentation

开放数组构造函数允许您直接在函数和过程调用中构造数组。

它们只能作为开放数组参数或变体开放数组参数传递。

因此,您不能使用开放数组构造函数来创建函数返回值。


如果数组中有固定数量的元素需要返回,可以使用动态数组构造函数:

Result := TStringDynArray.Create('string1', 'string2');

但是,这不适用于可变数量的元素。现在,我知道您问题中的示例在数组中只有恒定数量的元素。但我相信您会遇到需要比动态数组构造函数提供更多灵活性的情况。

如果您希望创建现有动态数组的副本并将其返回,请使用Copy

Result := Copy(SomeOtherDynamicArray);

当你手头有一个开放的数组时,这就会崩溃。您不能将开放数组传递给Copy。我个人认为这是一种耻辱,因为开放数组参数非常灵活和有用,我希望看到对它们的尽可能多的 RTL 支持。

因此,您最终不得不为这些情况编写辅助函数。您可以为每种数组类型编写一个专用的帮助程序,但这有点令人厌烦。这就是泛型派上用场的地方。为此,我有一个助手类。以下是相关摘录:

type
  TArray = class(Generics.Collections.TArray)
    ....
    class function Copy<T>(const Source: array of T): TArray<T>; overload; static;
    ....
  end;

class function TArray.Copy<T>(const Source: array of T): TArray<T>;
var
  i: Integer;
begin
  SetLength(Result, Length(Source));
  for i := 0 to high(Result) do begin
    Result[i] := Source[i];
  end;
end;

现在,这适用于您的字符串数组,也适用于任何其他类型。像这样称呼它:

Result := TArray.Copy<string>(SomeStringOpenArray);

需要指出的一个关键点是,我们使用的是动态数组的通用版本TArray&lt;string&gt;,而不是TStringDynArray。如果您想认真使用泛型,那么您必须这样做。这是因为TStringDynArray 的赋值与TArray&lt;string&gt; 或实际上声明为array of string 的任何其他类型不兼容。将代码库更改为始终使用 TArray&lt;T&gt; 会带来好处。

以防万一有人对这个助手类的其余部分感兴趣,这里是:

type
  TArray = class(Generics.Collections.TArray)
  private
    class function Comparison<T>(SortType: TSortType): TComparison<T>; static;
    class function Comparer<T>(const Comparison: TComparison<T>): IComparer<T>; static;
  public
    class procedure Swap<T>(var Left, Right: T); static;
    class procedure Reverse<T>(var Values: array of T); static;
    class function Reversed<T>(const Values: array of T): TArray<T>; static;
    class function Contains<T>(const Values: array of T; const Item: T; out ItemIndex: Integer): Boolean; overload; static;
    class function Contains<T>(const Values: array of T; const Item: T): Boolean; overload; static;
    class function IndexOf<T>(const Values: array of T; const Item: T): Integer; static;
    class function Sorted<T>(var Values: array of T; SortType: TSortType; Index, Count: Integer): Boolean; overload; static;
    class function Sorted<T>(var Values: array of T; SortType: TSortType): Boolean; overload; static;
    class function Sorted<T>(var Values: array of T; const Comparison: TComparison<T>; Index, Count: Integer): Boolean; overload; static;
    class function Sorted<T>(var Values: array of T; const Comparison: TComparison<T>): Boolean; overload; static;
    class function Sorted<T>(GetValue: TFunc<Integer,T>; const Comparison: TComparison<T>; Index, Count: Integer): Boolean; overload; static;
    class procedure Sort<T>(var Values: array of T; SortType: TSortType; Index, Count: Integer); overload; static;
    class procedure Sort<T>(var Values: array of T; SortType: TSortType); overload; static;
    class procedure Sort<T>(var Values: array of T; const Comparison: TComparison<T>; Index, Count: Integer); overload; static;
    class procedure Sort<T>(var Values: array of T; const Comparison: TComparison<T>); overload; static;
    class function Copy<T>(const Source: array of T; Index, Count: Integer): TArray<T>; overload; static;
    class function Copy<T>(const Source: array of T): TArray<T>; overload; static;
    class procedure Move<T>(const Source: array of T; var Dest: array of T; Index, Count: Integer); overload; static;
    class procedure Move<T>(const Source: array of T; var Dest: array of T); overload; static;
    class function Concatenated<T>(const Source1, Source2: array of T): TArray<T>; overload; static;
    class function Concatenated<T>(const Source: array of TArray<T>): TArray<T>; overload; static;
    class procedure Initialise<T>(var Values: array of T; const Value: T); static;
    class procedure Zeroise<T>(var Values: array of T); static;
    class function GetHashCode<T>(const Values: array of T): Integer; overload; static;
    class function GetHashCode<T>(Values: Pointer; Count: Integer): Integer; overload; static;
  end;

class function TArray.Comparison<T>(SortType: TSortType): TComparison<T>;
var
  DefaultComparer: IComparer<T>;
begin
  DefaultComparer := TComparer<T>.Default;
  Result :=
    function(const Left, Right: T): Integer
    begin
      case SortType of
      stIncreasing:
        Result := DefaultComparer.Compare(Left, Right);
      stDecreasing:
        Result := -DefaultComparer.Compare(Left, Right);
      else
        RaiseAssertionFailed(Result);
      end;
    end;
end;

class function TArray.Comparer<T>(const Comparison: TComparison<T>): IComparer<T>;
begin
  Result := TComparer<T>.Construct(Comparison);
end;

class procedure TArray.Swap<T>(var Left, Right: T);
var
  temp: T;
begin
  temp := Left;
  Left := Right;
  Right := temp;
end;

class procedure TArray.Reverse<T>(var Values: array of T);
var
  bottom, top: Integer;
begin
  bottom := 0;
  top := high(Values);
  while top>bottom do begin
    Swap<T>(Values[bottom], Values[top]);
    inc(bottom);
    dec(top);
  end;
end;

class function TArray.Reversed<T>(const Values: array of T): TArray<T>;
var
  i, j, Count: Integer;
begin
  Count := Length(Values);
  SetLength(Result, Count);
  j := Count-1;
  for i := 0 to Count-1 do begin
    Result[i] := Values[j];
    dec(j);
  end;
end;

class function TArray.Contains<T>(const Values: array of T; const Item: T; out ItemIndex: Integer): Boolean;
var
  DefaultComparer: IEqualityComparer<T>;
  Index: Integer;
begin
  DefaultComparer := TEqualityComparer<T>.Default;
  for Index := 0 to high(Values) do begin
    if DefaultComparer.Equals(Values[Index], Item) then begin
      ItemIndex := Index;
      Result := True;
      exit;
    end;
  end;
  ItemIndex := -1;
  Result := False;
end;

class function TArray.Contains<T>(const Values: array of T; const Item: T): Boolean;
var
  ItemIndex: Integer;
begin
  Result := Contains<T>(Values, Item, ItemIndex);
end;

class function TArray.IndexOf<T>(const Values: array of T; const Item: T): Integer;
begin
  Contains<T>(Values, Item, Result);
end;

class function TArray.Sorted<T>(var Values: array of T; SortType: TSortType; Index, Count: Integer): Boolean;
begin
  Result := Sorted<T>(Values, Comparison<T>(SortType), Index, Count);
end;

class function TArray.Sorted<T>(var Values: array of T; SortType: TSortType): Boolean;
begin
  Result := Sorted<T>(Values, Comparison<T>(SortType));
end;

class function TArray.Sorted<T>(var Values: array of T; const Comparison: TComparison<T>; Index, Count: Integer): Boolean;
var
  i: Integer;
begin
  for i := Index+1 to Index+Count-1 do begin
    if Comparison(Values[i-1], Values[i])>0 then begin
      Result := False;
      exit;
    end;
  end;
  Result := True;
end;

class function TArray.Sorted<T>(var Values: array of T; const Comparison: TComparison<T>): Boolean;
begin
  Result := Sorted<T>(Values, Comparison, 0, Length(Values));
end;

class function TArray.Sorted<T>(GetValue: TFunc<Integer, T>; const Comparison: TComparison<T>; Index, Count: Integer): Boolean;
var
  i: Integer;
begin
  for i := Index+1 to Index+Count-1 do begin
    if Comparison(GetValue(i-1), GetValue(i))>0 then begin
      Result := False;
      exit;
    end;
  end;
  Result := True;
end;

class procedure TArray.Sort<T>(var Values: array of T; SortType: TSortType; Index, Count: Integer);
begin
  Sort<T>(Values, Comparison<T>(SortType), Index, Count);
end;

class procedure TArray.Sort<T>(var Values: array of T; SortType: TSortType);
begin
  Sort<T>(Values, SortType, 0, Length(Values));
end;

class procedure TArray.Sort<T>(var Values: array of T; const Comparison: TComparison<T>; Index, Count: Integer);
begin
  if not Sorted<T>(Values, Comparison, Index, Count) then begin
    Sort<T>(Values, Comparer<T>(Comparison), Index, Count);
  end;
end;

class procedure TArray.Sort<T>(var Values: array of T; const Comparison: TComparison<T>);
begin
  Sort<T>(Values, Comparison, 0, Length(Values));
end;

class function TArray.Copy<T>(const Source: array of T; Index, Count: Integer): TArray<T>;
var
  i: Integer;
begin
  SetLength(Result, Count);
  for i := 0 to high(Result) do begin
    Result[i] := Source[i+Index];
  end;
end;

class function TArray.Copy<T>(const Source: array of T): TArray<T>;
var
  i: Integer;
begin
  SetLength(Result, Length(Source));
  for i := 0 to high(Result) do begin
    Result[i] := Source[i];
  end;
end;

class procedure TArray.Move<T>(const Source: array of T; var Dest: array of T; Index, Count: Integer);
var
  i: Integer;
begin
  for i := 0 to Count-1 do begin
    Dest[i] := Source[i+Index];
  end;
end;

class procedure TArray.Move<T>(const Source: array of T; var Dest: array of T);
var
  i: Integer;
begin
  for i := 0 to high(Source) do begin
    Dest[i] := Source[i];
  end;
end;

class function TArray.Concatenated<T>(const Source1, Source2: array of T): TArray<T>;
var
  i, Index: Integer;
begin
  SetLength(Result, Length(Source1)+Length(Source2));
  Index := 0;
  for i := low(Source1) to high(Source1) do begin
    Result[Index] := Source1[i];
    inc(Index);
  end;
  for i := low(Source2) to high(Source2) do begin
    Result[Index] := Source2[i];
    inc(Index);
  end;
end;

class function TArray.Concatenated<T>(const Source: array of TArray<T>): TArray<T>;
var
  i, j, Index, Count: Integer;
begin
  Count := 0;
  for i := 0 to high(Source) do begin
    inc(Count, Length(Source[i]));
  end;
  SetLength(Result, Count);
  Index := 0;
  for i := 0 to high(Source) do begin
    for j := 0 to high(Source[i]) do begin
      Result[Index] := Source[i][j];
      inc(Index);
    end;
  end;
end;

class procedure TArray.Initialise<T>(var Values: array of T; const Value: T);
var
  i: Integer;
begin
  for i := 0 to high(Values) do begin
    Values[i] := Value;
  end;
end;

class procedure TArray.Zeroise<T>(var Values: array of T);
begin
  Initialise<T>(Values, Default(T));
end;

{$IFOPT Q+}
  {$DEFINE OverflowChecksEnabled}
  {$Q-}
{$ENDIF}
class function TArray.GetHashCode<T>(const Values: array of T): Integer;
// see http://stackoverflow.com/questions/1646807 and http://stackoverflow.com/questions/11294686
var
  Value: T;
  EqualityComparer: IEqualityComparer<T>;
begin
  EqualityComparer := TEqualityComparer<T>.Default;
  Result := 17;
  for Value in Values do begin
    Result := Result*37 + EqualityComparer.GetHashCode(Value);
  end;
end;

class function TArray.GetHashCode<T>(Values: Pointer; Count: Integer): Integer;
// see http://stackoverflow.com/questions/1646807 and http://stackoverflow.com/questions/11294686
var
  Value: ^T;
  EqualityComparer: IEqualityComparer<T>;
begin
  EqualityComparer := TEqualityComparer<T>.Default;
  Result := 17;
  Value := Values;
  while Count>0 do begin
    Result := Result*37 + EqualityComparer.GetHashCode(Value^);
    inc(Value);
    dec(Count);
  end;
end;
{$IFDEF OverflowChecksEnabled}
  {$Q+}
{$ENDIF}

【讨论】:

  • 请不要因为我迂腐而杀了我,但第一行 IMNSHO 上真的不应该有冒号。
  • @Andreas 我没有杀了你,而是为你鼓掌。在我看来,迂腐是一种美好而令人向往的特质。
【解决方案3】:

问题

function MyFunc: TStringDynArray;
begin
  Result := ['Data1', 'Data2'];
end;

['Data1', 'Data2'] 被解释为set

我有时会使用以下便利功能(但通常不在性能关键部分):

function MakeStringArray(const Strings: array of string): TStringDynArray;
var
  i: Integer;
begin
  SetLength(Result, Length(Strings));
  for i := Low(Strings) to High(Strings) do
    Result[i] := Strings[i];
end {MakeStringArray};

使用它,您可以如下重写您的第一个示例:

function MyFunc: TStringDynArray;
....
function MyFunc: TStringDynArray;
begin
    Result := MakeStringArray(['Data1', 'Data2']);
end;

但由于您使用的是 XE3,因此最好使用 TStringDynArray.Create,例如 Andreas Rejbrand suggests

【讨论】:

  • 是的,尽管您希望编译器检测返回类型并知道它是一个动态数组。总的来说,对我来说似乎有点限制。
  • 虽然['Data1', 'Data2'] 在语法上会被解释为一个集合是对的,但它不会编译,因为集合不能将字符串作为它们的“基本类型”。
  • @AndreasRejbrand:没错。编译器引发的错误明确提到它需要一个序数类型。这个问题基本上是由于重复使用 [...] 符号:它们可以表示集合和数组。在这种情况下,编译器不够聪明,看不出它应该是一个数组,而不是一个集合。
  • +1 提到了在动态数组构造函数出现之前必须做的事情。
猜你喜欢
  • 2021-08-23
  • 2019-10-12
  • 1970-01-01
  • 1970-01-01
  • 2023-01-05
  • 1970-01-01
  • 2022-01-04
  • 2016-09-28
  • 2020-07-06
相关资源
最近更新 更多