通过启动和长度说明引用传递静态/动态数组片 [英] Passing slice of a static/dynamic array by reference with start and length specifier

查看:202
本文介绍了通过启动和长度说明引用传递静态/动态数组片的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

传递数组(动态或静态)来的方法/过程/函数与<一个href=\"http://docwiki.embarcadero.com/RADStudio/XE3/en/Parameters_%28Delphi%29#Open_Array_Parameters\"><$c$c>open数组参数 ,
   声明可以是这样的:

Passing arrays (dynamic or static) to methods/procedures/functions with open array parameters, declaration can look like this:

procedure WorkWithArray( const anArray : array of Integer);
(* or procedure WorkWithArray( var anArray : array of Integer); *)
var
  i : Integer;
begin
  for i := Low(anArray) to High(anArray) do
  begin
    // Do something with the "open array" anArray
    WriteLn(anArray[i]);
  end;
end;

...
var
  staticArray : array[0..2] of Integer;
  dynArray : array of integer;
  dynArrayG : TArray<Integer>;
begin
  SetLength(dynArray,10);
  SetLength(dynArrayG,10);

  WorkWithArray(staticArray);  // Using a static array
  WorkWithArray(dynArray);     // Using a dynamic array
  WorkWithArray(dynArrayG);    // Using a dynamic generic array
  ...
end;

传递数组像这样在整个德尔福RTL用了一个很常见的成语,其中包括用于处理数据数组一些非常优化的函数/过程。

Passing arrays like this is a very common idiom used throughout the Delphi RTL, including some very optimized functions/procedures for handling arrays of data.

假设我们需要调用 WorkWithArray 与我们的阵列的子区间。然后我们可以使用的内在 切片() 功能。

Suppose we need to call WorkWithArray with a subrange of our arrays. We can then use the intrinsic Slice() function.

初​​没有偏移,从第一索引

First without an offset, starting with first index:

Type
  // Helper declarations
  TIntLongArray = array[0..MaxInt div SizeOf(Integer) - 1] of integer;
  PIntLongArray = ^TIntLongArray;

WorkWithArray(Slice(staticArray,2)); // No type cast needed for static arrays
WorkWithArray(Slice(PIntLongArray(@dynArray)^,2));
WorkWithArray(Slice(PIntLongArray(@dynArrayG)^,2));

请注意:动态数组不直接插入切片()功能,
  看到
切片不与动态数组合作
  因此,与类型转换的变通办法已被使用。

Note: dynamic arrays does not fit directly into the Slice() function, see "Slice does not work with dynamic arrays". So the workaround method with type casting has to be used.

如果我们想要一个子范围不是从第一个元素开始?工作

What if we want to work with a subrange not starting from the first element?

可行,以及:

WorkWithArray(Slice(PIntLongArray(@staticArray[1])^,2));
WorkWithArray(Slice(PIntLongArray(@dynArray[1])^,2));
WorkWithArray(Slice(PIntLongArray(@dynArrayG[1])^,2));

注意:的总和的偏移和片必须不超过数组的元素计数

Note : the sum of the offset and the slice must not exceed the element count of the array.

我知道,使用复制(myarray的,X1,X2)能在输入被声明为常量的情况下使用,
     但是这将使该数组的副本,是大型阵列ineffiecient。 (带以及堆栈溢出的风险)。

I know that using Copy(myArray,x1,x2) could be used in cases where the input is declared as a const, but this will make a copy of the the array, and is ineffiecient for large arrays. (With risk of stack overflow as well).

最后,我的问题:

虽然此展示了一个方法通过用起始索引和长度说明符引用传递阵列的子范围,
  它看起来有点别扭。
  有没有更好的替代品,如果是这样如何?

While this demonstrates a way to pass a subrange of an array by reference with a start index and a length specifier, it looks a bit awkward. Are there better alternatives and if so how?

推荐答案

更新查看了一下下来了仿制药的解决方案。

Updated See a bit down for a generics solution.

下面是一个封装所需的功能,它驻留在一个先进的记录声明为类函数内的偏移量类型转换的替代品。
除了隐藏式投,偏移范围内对数组的高指标检查。

Here is an alternative that encapsulates the type cast needed for the offset inside a function, which resides in an advanced record declared as a class function. Besides hiding the type cast, the offset is range checked against the high index of the array.

更多类型可以根据需要添加。

More types can be added if needed.

Type
  SubRange = record
    Type
      TIntLongArray = array[0..MaxInt div SizeOf(Integer) - 1] of integer;
      PIntLongArray = ^TIntLongArray;
      TByteLongArray = array[0..MaxInt div SizeOf(Byte) - 1] of Byte;
      PByteLongArray = ^TByteLongArray;

    class function Offset( const anArray : array of Integer; 
                                 offset  : Integer) : PIntLongArray; overload; static;
    class function Offset( const anArray : array of Byte; 
                                 offset  : Integer) : PByteLongArray; overload; static;
    // ToDo: Add more types ...
  end;

class function SubRange.Offset(const anArray : array of Integer; 
                                     offset  : Integer): PIntLongArray;
begin
  Assert(offset <= High(anArray));
  Result := PIntLongArray(@anArray[offset]);
end;

class function SubRange.Offset(const anArray : array of Byte; 
                                     offset  : Integer): PByteLongArray;
begin
  Assert(offset <= High(anArray));
  Result := PByteLongArray(@anArray[offset]);
end;

注意:的总和的偏移和片必须不超过数组的元素计数

Note : the sum of the offset and the slice must not exceed the element count of the array.

调用示例:

WorkWithArray( Slice(SubRange.Offset(staticArray,1)^,2));
WorkWithArray( Slice(SubRange.Offset(dynArray,1)^,2));
WorkWithArray( Slice(SubRange.Offset(dynArrayG,1)^,2));

虽然这看起来更好,我仍然不相信这是最佳的解决方案。

While this looks better, I'm still not convinced this is the optimal solution.

更新

在编写上述溶液中,我有一个泛型解决方案为最终目标。

When writing the above solution, I had a generics solution as the ultimate goal.

下面是利用匿名方法和泛型实施回答的切片(anArray,则startIndex,计数)
方法可以与静态和动态阵列被使用。

Here is an answer that utilizes anonymous methods and generics to implement a Slice(anArray,startIndex,Count) method that can be used with both static and dynamic arrays.

一个直仿制药的解决方案将依靠范围检查每把它用在这里被关闭,
这将不会是一个pretty溶液。
其原因是,中SizeOf(T)不能用于申报最大尺寸的静态数组类型:

A straight generics solution would rely on range checking be turned off at every placed where it was used, and that would not be a pretty solution. The reason is that SizeOf(T) could not be used to declare a static array type of maximum size:

TGenericArray =阵列[0..MaxInt格中SizeOf(T) - 1]的T; //一下SizeOf(T)没有得到解决。

TGenericArray = array[0..MaxInt div SizeOf(T) - 1] of T; // SizeOf(T) not resolved

因此​​,我们将不得不使用:

So we would have to use:

TGenericArray =数组[0..0]的T;

TGenericArray = array[0..0] of T;

来代替。而这将触发范围检查,当它打开时,对指数> 0

instead. And this triggers the range check when it is on, for index > 0.

解决方案

但问题可以通过另一种策略来解决,回调或一个更现代的术语是控制反转 (IOC)或 Dependeny注射(DI)。
这个概念是最好的,不要给我打电话,我们叫你解释说。

But the problem could be solved by another strategy, callbacks or a more modern terminology would be Inversion of Control (IoC) or Dependeny Injection (DI). The concept is best explained with, "Don't call me, we call you".

而不是使用直接的功能,我们通过运营code作为一个匿名方法连同所有参数。
现在范围检查问题是包含在切片&LT内; T&GT;

Instead of using a direct function, we pass the operational code as an anonymous method together with all parameters. Now the range check problem is contained within the Slice<T> frame.

Slice<Integer>.Execute(
  procedure(const arr: array of Integer)
  begin
    WriteLn(Math.SumInt(arr));
  end, dArr, 2, 7);


unit uGenericSlice;

interface

type
  Slice<T> = record
  private
    type
      PGenericArr = ^TGenericArr;
      TGenericArr = array [0..0] of T;
  public
    type
      TConstArrProc = reference to procedure(const anArr: array of T);
    class procedure Execute(       aProc: TConstArrProc;
                             const anArray: array of T;
                                   startIndex,Count: Integer); static;
  end;

implementation

class procedure Slice<T>.Execute(aProc: TConstArrProc;
  const anArray: array of T; startIndex, Count: Integer);
begin
  if (startIndex <= 0) then
    aProc(Slice(anArray, Count))
  else
  begin
    // The expression PGenericArr(@anArray[startIndex]) can trigger range check error
    {$IFOPT R+}
      {$DEFINE RestoreRangeCheck}
      {$R-}
    {$ENDIF}
    Assert((startIndex <= High(anArray)) and (Count <= High(anArray)-startIndex+1),
      'Range check error');
    aProc(Slice(PGenericArr(@anArray[startIndex])^, Count));
    {$IFDEF RestoreRangeCheck}
      {$UNDEF RestoreRangeCheck}
      {$R+}
    {$ENDIF}
  end;
end;

end.

下面是一些示例使用情况:

Here are some example use cases:

program ProjectGenericSlice;

{$APPTYPE CONSOLE}

uses
  Math,
  uGenericSlice in 'uGenericSlice.pas';

function Sum(const anArr: array of Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i in anArr do
    Result := Result + i;
end;

procedure SumTest(const arr: array of integer);
begin
  WriteLn(Sum(arr));
end;

procedure TestAll;
var
  aProc: Slice<Integer>.TConstArrProc;
  dArr: TArray<Integer>;
  mySum: Integer;
const
  sArr: array [1 .. 10] of Integer = (
    1,2,3,4,5,6,7,8,9,10);

begin
  dArr := TArray<Integer>.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

  aProc :=
    procedure(const arr: array of Integer)
    begin
      WriteLn(Sum(arr));
    end;

  // Test predefined anonymous method
  Slice<Integer>.Execute( aProc, dArr, 2, 7);

  // Test inlined anonymous method
  Slice<Integer>.Execute(
    procedure(const arr: array of Integer)
    begin
      WriteLn(Sum(arr));
    end, dArr, 2, 7);

  // Test call to Math.SumInt
  Slice<Integer>.Execute(
    procedure(const arr: array of Integer)
    begin
      WriteLn(Math.SumInt(arr));
    end, dArr, 2, 7);

  // Test static array with Low(sArr) > 0
  Slice<Integer>.Execute(
    procedure(const arr: array of Integer)
    begin
      WriteLn(Sum(arr));
    end, sArr, 3 - Low(sArr), 7);

  // Using a real procedure
  Slice<Integer>.Execute(
    SumTest, // Cannot be nested inside TestAll
    dArr, 2, 7);

  // Test call where result is passed to local var
  Slice<Integer>.Execute(
    procedure(const arr: array of Integer)
    begin
      mySum := Math.SumInt(arr);
    end, dArr, 2, 7);
  WriteLn(mySum);

end;

begin
  TestAll;
  ReadLn;
end.

这篇关于通过启动和长度说明引用传递静态/动态数组片的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆