For..in..do 문

For..in..do 문

Free Pascal은 버전 2.4.2부터 For..in 루프 구성을 지원해요. for..in 루프는 열거 가능한 루프 변수로 고정된 횟수만큼 뭔가를 계산하고 싶을 때 사용해요.

출처: 문서

본문

여기서 Statement는 복합 문일 수 있어요. enumerable고정된 개수의 요소로 이루어진 표현식이어야 해요. 루프 변수는 각 요소에 차례로 대응되고, do 키워드 뒤의 문이 실행돼요.

열거 가능한 표현식은 다섯 가지 경우일 수 있어요.

  • 열거형 타입 식별자: 열거형의 모든 요소에 대해 루프를 돌아요. 제어 변수는 열거형 타입이어야 해요.
  • 집합 값: 집합의 모든 요소에 대해 루프를 돌아요. 제어 변수는 집합의 기본 타입이어야 해요.
  • 배열 값: 배열의 모든 요소에 대해 루프를 돌며, 제어 변수는 배열 요소와 같은 타입이어야 해요. 특별한 경우로 문자열은 문자의 배열로 간주돼요.
  • 열거 가능한 클래스/객체/확장 레코드 인스턴스: IEnumeratorIEnumerable 인터페이스를 지원하는 구조 타입의 인스턴스예요. 이 경우 제어 변수 타입은 IEnumerator.GetCurrent 반환 값의 타입과 같아야 해요.
  • 열거자 연산자(enumerator operator)가 정의된 타입: 그러한 연산자는 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는 집합의 기본 타입과 같은 타입이라는 점을 주목하세요.

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.

문자열 타입은 문자의 배열과 동일하므로, 문자열을 for..in 루프에 쓸 수 있어요. 다음 코드는 알파벳의 모든 문자를 각각 한 줄에 출력해요.

Var
   c : char;
 begin
  for c in 'abcdefghijklmnopqrstuvwxyz' do
    writeln(c);
 end.

참고로 다차원 배열도 지원돼요.

uses
   SysUtils;
 type
   TTestStringArray = array[0..10] of String;
 Var
   TwoD : array[0..3] of TTestStringArray;
 var
   i,j : integer;
   S : String;
 begin
   for i:=0 to 3 do
     for j:=0 to 10 do
       TwoD[i,J]:=Format('%.2dx%.2d',[i,j]);
   for S in twod do
     Writeln(S);
 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;

GetEnumerator 메서드가 반환한 클래스에는 Current 프로퍼티와 MoveNext 메서드가 반드시 있어야 해요. Current 프로퍼티의 실제 타입이 TObject일 필요는 없어요. in 피연산자로 클래스 인스턴스를 갖는 for..in 루프를 만나면, 컴파일러는 각각의 조건을 확인해요.

  • 열거 가능한 표현식의 클래스가 GetEnumerator 메서드를 구현하는지
  • GetEnumerator의 결과가 다음 메서드를 가진 클래스인지
Function MoveNext : Boolean
  • GetEnumerator의 결과가 다음 읽기 전용 프로퍼티를 가진 클래스인지
Property Current : AType;
  • 프로퍼티의 타입이 for..in 루프의 제어 변수 타입과 일치하는지

열거 가능한 클래스가 실제로 IEnumeratorIEnumerable 인터페이스를 선언할 필요는 없어요. 컴파일러는 위 검사로 이 인터페이스들이 있는지를 감지해요. 이 인터페이스들은 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"

위 코드는 다음 코드와 동일해요.

{$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 operator) 로 거의 모든 타입을 열거하는 거예요. 열거자 연산자는 위의 IEnumerator 방식과 같은 시그니처를 가진 클래스를 돌려줘야 해요. 다음 코드는 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.

이 루프는 열거 가능한 값보다 작거나 같은 모든 0이 아닌 짝수를 출력해요. (예시의 경우 2와 4예요.)

열거자 연산자를 정의할 때는 주의해야 해요. 컴파일러는 열거 가능한 표현식에 대해 사용 가능한 첫 번째 열거자 연산자를 찾아서 사용해요. 클래스의 경우 이것은 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 루프와 마찬가지로, 루프 안에서 루프 제어 변수의 값을 바꾸는 것(값을 대입하는 것)은 허용되지 않아요.

더 알아보기