Move()在动态字符串数组中插入/删除项

sh7euo9m  于 2022-09-21  发布在  其他
关注(0)|答案(8)|浏览(131)

使用System.Move()从字符串数组中插入/删除项不像从其他简单数据类型数组中插入/删除项那么容易。问题是..。字符串是在Delphi中计算的引用。在引用计数的数据类型上使用Move()需要对内部编译器行为有更深入的了解。

这里有没有人能解释一下实现这一点所需的步骤,或者更好地使用一些代码片段,或者在互联网上给我一个很好的参考资料?

哦,请不要告诉我使用“懒惰但缓慢的方式”,也就是,for loop,我知道的。

mznpcxlj

mznpcxlj1#

在此之前,我演示了如何从动态数组中删除项:

在那篇文章中,我从以下代码开始:

type
  TXArray = array of X;

procedure DeleteX(var A: TXArray; const Index: Cardinal);
var
  ALength: Cardinal;
  i: Cardinal;
begin
  ALength := Length(A);
  Assert(ALength > 0);
  Assert(Index < ALength);
  for i := Index + 1 to ALength - 1 do
    A[i - 1] := A[i];
  SetLength(A, ALength - 1);
end;

使用该代码,您“不会出错”。为X使用您想要的任何值;在您的情况下,将其替换为string。如果你想变得更花哨,使用Move,那么也有办法做到这一点。

procedure DeleteX(var A: TXArray; const Index: Cardinal);
var
  ALength: Cardinal;
  TailElements: Cardinal;
begin
  ALength := Length(A);
  Assert(ALength > 0);
  Assert(Index < ALength);
  Finalize(A[Index]);
  TailElements := ALength - Index;
  if TailElements > 0 then
    Move(A[Index + 1], A[Index], SizeOf(X) * TailElements);
  Initialize(A[ALength - 1]);
  SetLength(A, ALength - 1);
end;

由于Xstring,因此Finalize调用等同于将空字符串赋给该数组元素。不过,我在这段代码中使用Finalize,因为它适用于所有数组元素类型,甚至包括记录、接口、字符串和其他数组的类型。

对于插入,您只需将事物移动到相反的方向:

procedure InsertX(var A: TXArray; const Index: Cardinal; const Value: X);
var
  ALength: Cardinal;
  TailElements: Cardinal;
begin
  ALength := Length(A);
  Assert(Index <= ALength);
  SetLength(A, ALength + 1);
  Finalize(A[ALength]);
  TailElements := ALength - Index;
  if TailElements > 0 then begin
    Move(A[Index], A[Index + 1], SizeOf(X) * TailElements);
  Initialize(A[Index]);
  A[Index] := Value;
end;

当您要执行超出语言范围的操作时,请使用Finalize,例如使用非类型安全的Move过程覆盖编译器托管类型的变量。当您重新输入语言的定义部分时,请使用Initialize。(该语言定义了使用SetLength扩展或缩小数组时发生的情况,但它不定义如何在不使用字符串赋值语句的情况下复制或删除字符串。)

xlpyo6sf

xlpyo6sf2#

您没有说明保持数组元素的顺序是否重要。如果顺序不相关,你可以像这样做非常非常快

procedure RemoveRecord(Index: integer);  
begin
 FRecords[Index]:= FRecords[High(FRecords)];  { Copy the last element over the 'deleted' element }
 SetLength(FRecords, Length(FRecords)-1);     { Cut the last element }
end;

{ I haven't tested the code to see it compiles, but you got the idea anyway... }

列表排序

如果您有一个庞大的列表需要用户修改,您可以使用类似于上面的方法(打破列表顺序)。当用户完成编辑(在多次删除之后)时,您向其显示一个名为“Sort List”的按钮。现在他可以做冗长的(排序)操作了。
当然,上面我假设您的列表可以按某个参数进行排序。

列表自动排序

另一种选择是自动执行分拣过程。当用户从列表中删除内容时,启动计时器。如果用户不断删除项目,请继续重置计时器。当计时器设法触发事件时,执行排序,停止计时器。

v1uwarro

v1uwarro3#

要插入字符串,只需在数组(指针数组)的末尾添加一个字符串(惰性方法),然后使用Move更改该数组(指针数组)的元素顺序。

hmtdttj4

hmtdttj44#

如果我想在字符串列表的中间插入一个字符串,我会使用TStringList.Insert。(它使用System.Move快速完成此操作。)

使用数组而不是TStringList有什么特别的原因吗?

fkvaft9z

fkvaft9z5#

在处理它之前,对它调用UniqueString()。

http://docwiki.embarcadero.com/VCL/en/System.UniqueString

然后,您就有了一个只有一个引用的字符串。

DELETE和INSERT也不太可能做到这一点,而且我怀疑你会更快。

mnemlml8

mnemlml86#

只是想为将来来这里的任何人添加这一点。

修改Rob的代码时,我想出了一种使用较新的TArray<T>类型结构的方法。

type
  TArrayExt = class(TArray)
    class procedure Delete<T>(var A: TArray<T>; const Index: Cardinal; Count: Cardinal = 1);
  end;

implementation

class procedure TArrayExt.Delete<T>(var A: TArray<T>; const Index: Cardinal;
    Count: Cardinal = 1);
var
  ALength: Cardinal;
  i: Cardinal;
begin
  ALength := Length(A);
  Assert(ALength > 0);
  Assert(Count > 0);
  Assert(Count <= ALength - Index);
  Assert(Index < ALength);

  for i := Index + Count to ALength - 1 do
    A[i - Count] := A[i];

  SetLength(A, ALength - Count);
end;

对于插入物也可以做类似的事情。

(我并不希望这个答案被标记为答案,只是希望提供一个太长的例子,无法放入对Rob优秀答案的评论中。)

(已修复以回应Rob下面的评论。)

n7taea2i

n7taea2i7#

Move()可以很好地处理引用计数的类型,如字符串或接口,并且实际上在Delphi的数组和列表中内部使用。但是,在一般情况下,由于管理记录功能,move()不再有效。

62lalag4

62lalag48#

如果您使用System.Move将项放入字符串数组中,您应该注意到,在移动之前(现在被覆盖)的字符串具有引用计数-1(对于常量字符串),或者>0(对于变量字符串)。不应该更改常量字符串,但应该相应地处理变量字符串:您应该手动降低它们的引用计数(在它们被覆盖之前!)。要做到这一点,您应该尝试如下所示:

Dec(PStrRec(IntPtr(SomeString)-12).refCnt);

但是,如果引用计数达到零,您还应该终止相关的内存-如果让它工作,Delphi本身就会做得更好-它是字符串的编译器魔术。哦,还有:如果您要复制的字符串与您要写入的字符串来自同一个数组,那么所需的管理将变得非常麻烦,而且非常快!

因此,如果以某种方式可以避免所有这些手动内务,我建议让Delphi自己处理。

相关问题