使用System.Move()从字符串数组中插入/删除项目并不像从其他简单数据类型中插入/删除它那么容易。问题是...字符串是在Delphi中计算的引用。在引用计数数据类型上使用Move()需要更深入的内部编译器行为知识。
有人可以在这里解释为实现这一目标所需的步骤,或者更好地使用一些代码段,或者引导我在互联网上找到一个好的参考资料吗?
哦,请不要告诉我使用“懒惰但缓慢的方式”,即循环,我知道。
答案 0 :(得分:18)
我在演示之前演示了如何从动态数组中删除项目:
在那篇文章中,我从以下代码开始:
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;
由于X
为string
,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
增长或缩小时会发生什么,但它没有定义如何在不使用字符串赋值语句的情况下复制或删除字符串。)
答案 1 :(得分:2)
要插入字符串,只需将一个字符串(懒惰的方式)添加到数组的末尾(这是一个指针数组),然后使用Move
来更改此数组元素的顺序(指针)。
答案 2 :(得分:2)
如果我想在字符串列表的中间插入一个字符串,我会使用TStringList.Insert。 (它使用System.Move快速完成。)
您使用数组而不是TStringList的任何特殊原因?
答案 3 :(得分:2)
您没有说明是否将数组元素保持在相同的顺序非常重要。 如果订单不相关,你可以这么快就这样:
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;
{我还没有测试代码看它编译,但无论如何你都有了想法......}
编辑一个巨大的清单:
如果您有一个需要用户修改的巨大列表,您可以使用与上面类似的方法(中断列表顺序)。当用户完成编辑(多次删除后)时,会显示一个名为“排序列表”的按钮。现在他可以进行冗长的(排序)操作
当然,我假设您的列表可以按某个参数排序。
答案 4 :(得分:1)
在搞乱之前调用UniqueString()。
http://docwiki.embarcadero.com/VCL/en/System.UniqueString
然后你有一个带有单个引用的字符串。
这就是删除和插入的可能性,我怀疑你会更快。
答案 5 :(得分:0)
只是想为将来来这里的人添加这个。
修改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的评论。)
答案 6 :(得分:-1)
如果使用System.Move将项目放入字符串数组中,您应该知道在移动之前(现在被覆盖)的字符串对于常量字符串的引用计数为-1,或者&GT; 0表示变量字符串。不应更改常量字符串,但应相应地处理变量字符串:您应手动降低其引用计数(在它们被覆盖之前!)。要做到这一点,你应该尝试这样的事情:
Dec(PStrRec(IntPtr(SomeString)-12).refCnt);
但是如果引用计数达到零,那么你也应该最终确定相关的内存 - 如果你让Delphi本身的字符串使用它的编译器魔法,Delphi本身会做得更好。哦,还有:如果你要复制的字符串来自你写入的同一个数组,那么所需的管理变得非常麻烦,非常快!
因此,如果以某种方式避免所有这些手动内务管理,我建议让Delphi自己处理。