Добавление одного элемента в динамический массив

Это очень частая картина во всем моем коде:

SetLength(SomeDynamicArray, Length(SomeDynamicArray)+1);
SomeDynamicArray[High(SomeDynamicArray)] := NewElement;

Нет ли способа сделать это в одной строке?

Редактировать: Это невероятно неэффективно. Я знаю. Я использую динамические массивы (в моем собственном коде, в моих личных проектах, которые я использую только), потому что они самые простые в использовании, и мне просто нужно сделать все с минимальным количеством кода.

delphi,

9

Ответов: 7


18 ов принято

Вот взлом с дженериками, который работает только с TArray<T>:

type
  TAppender<T> = class
    class procedure Append(var Arr: TArray<T>; Value: T);
  end;

class procedure TAppender<T>.Append;
begin
  SetLength(Arr, Length(Arr)+1);
  Arr[High(Arr)] := Value;
end;

Применение:

var
  TestArray: TArray<Integer>;

begin
  TAppender<Integer>.Append(TestArray, 5);
end.

11 ов

Каждый раз, когда вы вызываете SetLengthпамять, происходит перераспределение. Возможно, весь массив нужно скопировать в другое место. И вы, которые просто хотели добавить один элемент в массив!

В принципе: никогда не делайте этого. Есть два выхода из этого. Самый простой случай - если вы заранее знаете максимальный размер массива:

procedure Example1;
var
  data: array of string;
  ActualLength: integer;

  procedure AddElement(const Str: string);
  begin
    data[ActualLength] := Str;
    inc(ActualLength);
  end;

begin

  ActualLength := 0;
  SetLength(data, KNOWN_UPPER_BOUND);

  for ...
    while ...
      repeat ...
        AddElement(SomeString);

  SetLength(data, ActualLength);

end;

Вот пример практического примера этого подхода.

Если вы не знаете какую-либо верхнюю границу априори , а затем выделите большие куски:

procedure Example2;
const
  ALLOC_BY = 1024;
var
  data: array of string;
  ActualLength: integer;

  procedure AddElement(const Str: string);
  begin
    if ActualLength = length(data) then
      SetLength(data, length(data) + ALLOC_BY);

    data[ActualLength] := Str;
    inc(ActualLength);
  end;

begin

  ActualLength := 0;
  SetLength(data, ALLOC_BY);

  for ...
    while ...
      repeat ...
        AddElement(SomeString);

  SetLength(data, ActualLength);

end;

Этот второй подход реализован в время выполнения библиотеки TList<T>, TObjectList<T>, и TStringListт.д. Таким образом, когда вы используете эти классы, это прекрасно , чтобы добавить в список по одному элементу за раз.


4

Это анти-шаблон, который приводит к фрагментации памяти. Вместо этого используйте Generics.Collections.TList<T>метод Add для добавления новых элементов.

Для расширения массива и добавления элемента нет ни одного вкладыша. Вы можете создать свою собственную оболочку динамического массива с помощью дженериков, чтобы сделать это, если захотите. По сути, это то, что Generics.Collections.TList<T>есть.


2

Если у вас есть Delphi 2009 или более поздняя версия, и вы действительно хотите сократить этот фрагмент кода, вы можете попробовать что-то вроде

type
  DataArray<T> = record
    Data: array of T;
    procedure Append(const Value: T);
    function Count: integer;
  end;


{ DataArray<T> }

procedure DataArray<T>.Append(const Value: T);
begin
  SetLength(Data, length(Data) + 1);
  Data[high(Data)] := Value;
end;

function DataArray<T>.Count: integer;
begin
  result := length(Data);
end;

Тогда вы можете сделать

procedure TForm1.FormCreate(Sender: TObject);
var
  data: DataArray<string>;
begin
  data.Append('Alpha');
  data.Append('Beta');
  Caption := IntToStr(data.Count) + ': ' data.Data[0] + ' & ' + data.Data[1];
end;

2
MyList.Add(myobject);

IMO Динамические массивы следует использовать только тогда, когда во время компиляции вы не знаете точный размер массива, но во время выполнения, которое вы будете знать.

Если вам нужно постоянно манипулировать размером вашего массива, вы не должны использовать массив, но TList или один из его потомков, как упомянули другие: TObjectList, TInterfaceList, TStringList.Objects [] могут использоваться (и злоупотреблять) и есть и новые, и TList для примитивных типов. TList раньше был чем-то вроде боли, прежде чем дженерики были введены в Delphi - вам приходилось работать с указателями, но с generics: TList <T> это очень просто.

Кроме того, используйте свойство емкости любого созданного вами списка - он будет предварительно выделять заданный объем памяти, чтобы ваш код не вызывал большого объема памяти, чтобы ее жонглировать каждый раз, когда вы выполняете операцию в своем списке. (Если вы выходите за пределы выделенной вами емкости, диспетчер памяти даст вам больше памяти во время выполнения - вы не сработаете - см. Справку Delphi)

Дельфы,
Похожие вопросы