13.2.5 Оператор For..in..do

Вверх  Предыдущий  Следующий

Начиная с версии Free Pascal 2.4.2, поддерживает конструкцию цикла for..in. Цикл for..in используется в случае, если нужно что-то вычислять фиксированное число раз с перечислимой переменной. Синтаксический прототип выглядит следующим образом:


Оператор for

1315


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

Перечислимое выражение может быть одним из пяти вариантов:

1.Идентификатор типа перечисление. Цикл будет производиться над всеми элементами типа перечисления. Переменная цикла должна быть типа перечисления.

2.Значение типа множество. Цикл будет произведён со всеми элементами в множестве, переменная цикла должна быть основана на типе множество.

3.Значение типа массив. Цикл будет произведён над всеми элементами массива, а переменная цикла должна иметь тот же тип что элемент массива. Как частный случай, строка рассматривается как массив символов.

4.Экземпляр класса enumeratable (перечисления). Это экземпляр класса, который поддерживает интерфейсы IEnumerator и IEnumerable. В этом случае тип переменной цикла должен быть такой-же как тип значения возвращаемого IEnumerator.GetCurrent.

5.Любой тип, для которого определен оператор перечисления. Оператор должен возвращать класс перечисления (enumerator), который реализует интерфейс IEnumerator. Тип переменной цикла должен быть как тип возвращаемого значения перечислителем класса GetCurrent.

Простейший случай цикла for..in использует переменную перечисляемого типа:

Type

TWeekDay = (monday, tuesday, wednesday, thursday, friday,saturday,sunday);

 

Var

d : TWeekday;

begin

for d in TWeekday do writeln(d);

end.

Этот код выведет на экран все дни недели.

Выше приведённая конструкция for..in эквивалентна следующей конструкции for..to:

Type

TWeekDay = (monday, tuesday, wednesday, thursday, friday,saturday,sunday);

 

Var

d : TWeekday;

begin

for d:=Low(TWeekday) to High(TWeekday) do writeln(d);

end.

Второй случай цикла for..in, когда перечислимое выражение представляет собой множество, а затем цикл будет выполняться каждый раз для каждого элемента множества:

Type

TWeekDay = (monday, tuesday, wednesday, thursday, friday,saturday,sunday);

 

Var

Week : set of TWeekDay = [monday, tuesday, wednesday, thursday, friday];

d : TWeekday;

begin

for d in Week do writeln(d);

end.

Этот код выведет на экран все дни недели. Обратите внимание, что переменная d того же типа, что и множество (Week).

Выше приведённая конструкция for..in эквивалентна следующей конструкции for..to:

Type

TWeekDay = (monday, tuesday, wednesday, thursday, friday,saturday,sunday);

 

Var

Week : set of TWeekDay

      = [monday, tuesday, wednesday, thursday, friday];

d : TWeekday;

 

begin

for d:=Low(TWeekday) to High(TWeekday) do

      if d in Week then writeln(d);

end.

Третий вариант цикла for..in, когда перечислимое выражение является массивом:

var

a : Array[1..7] of string

  = ('monday','tuesday','wednesday','thursday',

      'friday','saturday','sunday');

 

Var

S : String;

begin

For s in a do Writeln(s);

end.

Этот код выведет на экран все дни недели т эквивалентен

var

a : Array[1..7] of string

  = ('monday','tuesday','wednesday','thursday',

      'friday','saturday','sunday');

Var

i : integer;

begin

for i:=Low(a) to high(a) do Writeln(a[i]);

end.

Тип string эквивалентен array of char, по этому строка может быть использована в цикле for..in. Этот код выведет на экран все буквы алфавита, каждая буква с новой строки:

Var

c : char;

begin

for c in 'abcdefghijklmnopqrstuvwxyz' do writeln(c);

end.

Четвертый вариант цикла for..in использует классы. Класс реализует интерфейс IEnumerable, который определяется следующим образом:

IEnumerable = interface(IInterface)

function GetEnumerator: IEnumerator;

end;

Фактический тип не обязательно должен возвращать GetEnumerator, но должен реализовать интерфейс IEnumerator, вместо этого, он может быть классом, который реализует методы IEnumerator:

IEnumerator = interface(IInterface)

function GetCurrent: TObject;

function MoveNext: Boolean;

procedure Reset;

property Current: TObject read GetCurrent;

end;

Свойство Current и метод MoveNext должен присутствовать в классе, возвращаемом методом GetEnumerator. Фактический тип свойства Current не обязательно должен быть TObject. Встретив for..in цикл с экземпляром класса, в качестве in операнда, компилятор проверит каждое из следующих условий:

Является ли класс в перечислимом выражении реализующим метод GetEnumerator

Является ли результат метода GetEnumerator классом с методом:
Function MoveNext : Boolean

Является ли результат метода GetEnumerator классом со свойством только для чтения:
Property Current : AType;

Тип свойства должен соответствовать типу переменной цикла цикла for..in.

Интерфейсы IEnumerator, IEnumerable не должны быть фактически объявлены в перечислимом классе: компилятор обнаруживает, присутствуют ли эти интерфейсы с помощью описанных выше проверок. Интерфейсы определены только для совместимости с Delphi и не используются внутри. (Также было бы невозможно обеспечить соблюдение их правильности).

Модуль Classes содержит ряд классов, которые содержат перечислители:

TFPList
Перечисляет все указатели в списке.

TList
Перечисляет все указатели в списке.

TCollection
Перечисляет все элементы коллекции.

TStringList
Перечисляет все строки в списке.

TComponent
Перечисляет все дочерние компоненты, принадлежащие компоненту.

Этот код выведет на экран все дни недели.

{$mode objfpc}

uses classes;

 

Var

Days : TStrings;

D : String;

begin

Days:=TStringList.Create;

try

  Days.Add('Monday');

  Days.Add('Tuesday');

  Days.Add('Wednesday');

  Days.Add('Thursday');

  Days.Add('Friday');

  Days.Add('Saturday');

  Days.Add('Sunday');

  For D in Days do Writeln(D);

Finally

  Days.Free;

end;

end.

Обратите внимание, что компилятор нуждается в безопасности типов: объявление D как целого числа приведет к ошибке компиляции:

testsl.pp(20,9) Error: Incompatible types: got "AnsiString" expected "LongInt"
testsl.pp (20,9) Ошибка: Типы несовместимы: получил "AnsiString" ожидая "LongInt"

Приведенный выше код эквивалентен следующему:

{$mode objfpc}

uses classes;

 

Var

Days : TStrings;

D : String;

E : TStringsEnumerator;

begin

Days:=TStringList.Create;

try

  Days.Add('Monday');

  Days.Add('Tuesday');

  Days.Add('Wednesday');

  Days.Add('Thursday');

  Days.Add('Friday');

  Days.Add('Saturday');

  Days.Add('Sunday');

  E:=Days.getEnumerator;

  try

    While E.MoveNext do

      begin

        D:=E.Current;

        Writeln(D);

      end;

  Finally

    E.Free;

  end;

Finally

  Days.Free;

end;

end.

Обе программы приведут к одному результату.

Пятая и последняя возможность использовать цикл for..in его можно использовать для перечисления практически любого типа, с помощью оператора enumerator (перечислитель). Оператор enumerator должен возвращать класс, который имеет ту же сигнатуру, что и IEnumerator в подходе выше. Следующий код будет определять enumerator (перечислитель) для типа Integer:

Type

 

TEvenEnumerator = Class

FCurrent : Integer;

FMax : Integer;

Function MoveNext : Boolean;

Property Current : Integer Read FCurrent;

end;

 

Function TEvenEnumerator.MoveNext : Boolean;

begin

FCurrent:=FCurrent+2;

Result:=FCurrent<=FMax;

end;

 

operator enumerator(i : integer) : TEvenEnumerator;

begin

Result:=TEvenEnumerator.Create;

Result.FMax:=i;

end;

 

var

I : Integer;

m : Integer = 4;

begin

For I in M do Writeln(i);

end.

Цикл выведет на экран все отличные от нуля четные числа меньше или равные перечислим. (как в примерах 2 и 4).

Необходимо соблюдать осторожность при определении операторов Enumerator: компилятор найдет и использовать первый же доступный оператор Enumerator для перечисляемого выражения. Для классов это означает, что метод GetEnumerator даже не рассматривается. Следующий код будет определять оператор перечислитель, который извлекает объекты из StringList:

{$mode objfpc}

uses classes;

 

Type

TDayObject = Class

  DayOfWeek : Integer;

  Constructor Create(ADayOfWeek : Integer);

end;

 

TObjectEnumerator = Class

  FList : TStrings;

  FIndex : Integer;

  Function GetCurrent : TDayObject;

  Function MoveNext: boolean;

  Property Current : TDayObject Read GetCurrent;

end;

 

Constructor TDayObject.Create(ADayOfWeek : Integer);

begin

DayOfWeek:=ADayOfWeek;

end;

 

Function TObjectEnumerator.GetCurrent : TDayObject;

begin

Result:=FList.Objects[Findex] as TDayObject;

end;

 

Function TObjectEnumerator.MoveNext: boolean;

begin

Inc(FIndex);

Result:=(FIndex<FList.Count);

end;

 

operator enumerator (s : TStrings) : TObjectEnumerator;

begin

Result:=TObjectEnumerator.Create;

Result.Flist:=S;

Result.FIndex:=-1;

end;

 

Var

Days : TStrings;

D : String;

O : TdayObject;

 

begin

Days:=TStringList.Create;

try

  Days.AddObject('Monday',TDayObject.Create(1));

  Days.AddObject('Tuesday',TDayObject.Create(2));

  Days.AddObject('Wednesday',TDayObject.Create(3));

  Days.AddObject('Thursday',TDayObject.Create(4));

  Days.AddObject('Friday',TDayObject.Create(5));

  Days.AddObject('Saturday',TDayObject.Create(6));

  Days.AddObject('Sunday',TDayObject.Create(7));

  For O in Days do Writeln(O.DayOfWeek);

Finally

  Days.Free;

end;

end.

Приведенный выше код выведет на экран название дня недели за неделю.

Если класс не поддерживает перечисление, компилятор выдаст сообщение об ошибке , когда он встретит цикл for...in.

Примечание:

Как и в цикле for..to, не разрешается изменять (т.е. присваивать) значение переменной цикла внутри цикла.