Копия викизеркало

Это зеркало вики с pascal.chat. Правка, история и вход — там.

for-in loop/ru

English · Français · Русский · 日本語

"for-in" цикл появился в delphi, начиная с версии 2005. Данная конструкция доступна сейчас с версии fpc 2.4.2.

Delphi и FPC реализация

Она имеет следующий синтаксис:

Цикл применимый к строкам

pascal
procedure StringLoop(S: String);
    var
      C: Char;
    begin
      for C in S do
        DoSomething(C);
    end;

Цикл применимый к массиву

pascal
procedure ArrayLoop(A: Array of Byte);
    var
      B: Byte;
    begin
      for B in A do
        DoSomething(B);
    end;

Цикл применимый к множеству

pascal
type
      TColor = (cRed, cGren, cBlue);
      TColors = set of TColor;
    procedure SetLoop(Colors: TColors);
    var
      Color: TColor;
    begin
      for Color in Colors do
        DoSomething(Color);
    end;

Применение к классам контейнерам

Для обхода элементов класса контейнера необходимо добавить специальный перечислитель. Перечислитель встраиваемый в класс представлен в следующем шаблоне:

pascal
TSomeEnumerator = class
    public
      function MoveNext: Boolean;
      property Current: TSomeType;
    end;

Для реализации перечислителя необходимы: метод MoveNext , который увеличивает позицию элемента перечисления и свойство Current , в котором возвращается выбранный вами тип данных.

Следующим шагом необходимо добавить к классу контейнеру специального метода GetEnumerator , который будет возвращать экземпляр перечислителя.

Пример:

pascal
type
      TEnumerableTree = class;
    
      TTreeEnumerator = class
      private
        FTree: TEnumerableTree;
        FCurrent: TNode;
      public
        constructor Create(ATree: TEnumerableTree); 
        function MoveNext: Boolean;
        property Current: TNode read FCurrent;
      end;
    
      TEnumerableTree = class
      public
        function GetEnumerator: TTreeEnumerator;
      end;
    
    constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
    begin
      inherited Create;
      FTree := ATree;
      FCurrent := nil;
    end;
    
    function TTreeEnumerator.MoveNext: Boolean;
    begin
      // получение следующего узла дерева
      if FCurrent = nil then
        FCurrent := FTree.GetFirstNode
      else
        FCurrent := FTree.GetNextNode(FCurrent);
      Result := FCurrent <> FTree.GetLastNode;
    end;
    
    function TEnumerableTree.GetEnumerator: TTreeEnumerator;
    begin
      Result := TTreeEnumerator.Create(Self);
    end;

После этого вы можете использовать следующий код:

pascal
procedure TreeLoop(ATree: TEnumerableTree);
    var
      ANode: TNode;
    begin
      for ANode in ATree do
        DoSomething(ANode);
    end;

Поддержка перечислителя встроена в основные классы: TList, TStrings, TCollection, TComponent, ...

Также поддержку перечислителя можно добавить в класс контейнер, если вы реализует в нём следующий интерфейс:

pascal
IEnumerable = interface(IInterface)
        function GetEnumerator: IEnumerator;
      end;

Интерфейс IEnumerator определён следующим образом:

pascal
IEnumerator = interface(IInterface)
        function GetCurrent: TObject;
        function MoveNext: Boolean;
        procedure Reset;
        property Current: TObject read GetCurrent;
      end;

Расширения FPC

Приведенные ниже примеры не поддерживаются Delphi, и предназначены только для FPC.

Перечисления и диапазоны

В Delphi невозможно использовать в цикле типы перечислений и диапазонов:

pascal
type
      TColor = (clRed, clBlue, clBlack);
      TRange = 'a'..'z';
    var
      Color: TColor;
      ch: Char;
    begin
      for Color in TColor do
        DoSomething(Color);
      for ch in TRange do
        DoSomethingOther(ch);
    end.

Объявление перечислителей

В Delphi также невозможно добавить перечислитель не изменяя класс и добавить перечислитель к следующим типам не-классы/объекты/записи/интерфейсы. В FPC добавление перечислитиля для любого типа производится новым оператором operator Enumerator. Смотрите следующий пример:

pascal
type
      TMyRecord = record F1: Integer; F2: array of TMyType; end;
      TMyArrayEnumerator = class
        constructor Create(const A: TMyRecord);
        function Current: TMyType;
        function MoveNext: Boolean;
      end;
    
      // Это новый встроенный оператор
      operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
      begin
        Result := TMyArrayEnumerator.Create(A);
      end;
    
    var
      A: MyRecord;
      V: TMyType
    begin
      for V in A do
        DoSomething(V);
    end.

Примером использования перечислений может быть следующая реализация эффективного перебора строк в кодировке UTF-8:

pascal
type
      TUTF8StringEnumerator = class
      private
        FByteIndex: Integer;
        FCharIndex: Integer;
      public
        constructor Create(const A: UTF8String);
        function Current: UTF8Char;
        function MoveNext: Boolean;
      end;
    
      operator Enumerator(A: UTF8String): TUTF8StringEnumerator;
      begin
        Result := TUTF8String.Create(A);
      end;
    
    var
      s: UTF8String;
      ch: UTF8Char;
      i: Integer;
    begin
      // Здесь требуется выполнить O(N^2) операций
      for i := 1 to Length(s) do
        DoSomething(ch[i]);
      // А здесь, только O(N) операций
      for ch in s do
        DoSomething(ch);
    end.

Использование имён идентификаторов отличных от MoveNext и Current

В Delphi вы должны использовать только функцию с именем 'MoveNext' и свойство с именем 'Current' для перечислений. В FPC можно использовать любые разрешенные имена. Для того, чтобы указать компилятору какая функция перечисляет элементы необходимо указать следующий модификатор 'enumerator MoveNext', а для свойства текущего элемента 'enumerator Current'. Смотрите следующий пример:

pascal
type
      { TMyListEnumerator }
    
      TMyListEnumerator = object
      private
        FCurrent: Integer;
      public
        constructor Create;
        destructor Destroy;
        function StepNext: Boolean; enumerator MoveNext;
        property Value: Integer read FCurrent; enumerator Current;
      end;
    
      TMyList = class
      end;
    
    { TMyListEnumerator }
    
    constructor TMyListEnumerator.Create;
    begin
      FCurrent := 0;
    end;
    
    destructor TMyListEnumerator.Destroy;
    begin
      inherited;
    end;
    
    function TMyListEnumerator.StepNext: Boolean;
    begin
      inc(FCurrent);
      Result := FCurrent <= 3;
    end;
    
    operator enumerator (AList: TMyList): TMyListEnumerator;
    begin
      Result.Create;
    end;
    
    var
      List: TMyList;
      i: integer;
    begin
      List := TMyList.Create;
      for i in List do
        WriteLn(i);
      List.Free;
    end.

Предлагаемые расширения

Выбор перечисления для использования

Возможно использовать различные перечисления. Например, вы можете обойти дерево, используя различный порядок. Хорошо известны алгоритмы: предварительный порядок, обратный порядок, симметричный порядок и обход в ширину. Поэтому, полезно иметь возможность выбора перечисления. Например, используя следующий синтаксис:

pascal
type
      TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst)
    procedure TraverseTree(Tree: TTree);
    var
      Node: TNode;
    begin
      // Вариант 1. Для экземляров класса мы можем вызвать метод Tree.GetEnumerator(teInOrder). 
      // Для классов мы можем вызвать метод класса
      for Node in Tree using GetEnumerator(teInOrder) do
        Dosomething(Node);
    
      // Вариант 2. Или мы можем вызвать глобальную функцию
      for Node in Tree using GetEnumerator(Tree, teInOrder) do
        Dosomething(Node);
    
      // Вариант 3. В предыдущем варианте 'in Tree' бесполезно, поэтому следующий код - это упрощенная форма:
      for Node using GetEnumerator(Tree, teInOrder) do
        Dosomething(Node);
    
      // Вариант 4. Мы можем попытаться избежать нового контекстного ключевого слова 'using', вызывая метод:
      for Node in Tree.GetSomeEnumerator(teInOrder) do
        Dosomething(Node);
      // но это принесет двусмысленность для компилятора, т.к. Tree.GetSomeEnumerator(teInOrder) может быть транслирован в
      // Tree.GetSomeEnumerator(teInOrder).GetEnumerator
      // Эта двусмысленность может быть разрешена проверкой, где класс применяет интерфейс IEnumerator
    end;
    
    // для базового типа мы вызовем только подходящую функцию
    procedure TraverseString(S: String);
    var
      C: Char;
    begin
      for C in S using GetReverseStringEnumerator(S) do
        DoSomething(C);
    end;

Получение Позиции перечисления, если возможно

Наконец, возможно извлечь любую информацию из интератора, кроме текущего элемента. Иногда любые данные, такие как текущий индекс, могут быть полезны:

pascal
type
      TUTF8StringEnumerator = class
      private
        FByteIndex: Integer;
        FCharIndex: Integer;
      public
        constructor Create(const A: UTF8String);
        function Current: UTF8Char;
        function CurrentIndex: Integer;
        function MoveNext: Boolean;
      end;
    
      operator GetEnumerator(A: UF8String): TUF8StringEnumerator;
      begin
        Result := TUF8String.Create(A);
      end;
    
    var
      s: UF8String;
      ch: UF8Char;
      i: Integer;
    begin
    
      // Неэффективный метод, как написано выше
      for i := 1 to Length(s) do
        Writeln(i, ': ', ch[i]);
    
      // Нормальный, но уродливый
      i := 1;
      for ch in s do begin
        Writeln(i, ': ', ch);
        Inc(i);
      end;
    
      // Предлагаемое расширение
      for ch in s index i do
        Writeln(i, ': ', ch);
    end.

Запомните, что индекс может вернуть произвольный тип, не обязательно целый тип. Например,в случае обхода дерева индекс может вернуть массив узлов на пути от корня к текущему узлу.

Ссылки


From the Free Pascal wiki, page "for-in loop/ru", as the Internet Archive kept it on 2025-04-18, last changed on 2020-02-16. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Версия 3, последнее изменение: WikiArchive, .