For..in..do 문
For..in..do 문
Free Pascal은 버전 2.4.2부터 For..in 루프 구성을 지원해요. for..in 루프는 열거 가능한 루프 변수로 고정된 횟수만큼 뭔가를 계산하고 싶을 때 사용해요.
출처: 문서
본문
여기서 Statement는 복합 문일 수 있어요. enumerable은 고정된 개수의 요소로 이루어진 표현식이어야 해요. 루프 변수는 각 요소에 차례로 대응되고, do 키워드 뒤의 문이 실행돼요.
열거 가능한 표현식은 다섯 가지 경우일 수 있어요.
- 열거형 타입 식별자: 열거형의 모든 요소에 대해 루프를 돌아요. 제어 변수는 열거형 타입이어야 해요.
- 집합 값: 집합의 모든 요소에 대해 루프를 돌아요. 제어 변수는 집합의 기본 타입이어야 해요.
- 배열 값: 배열의 모든 요소에 대해 루프를 돌며, 제어 변수는 배열 요소와 같은 타입이어야 해요. 특별한 경우로 문자열은 문자의 배열로 간주돼요.
- 열거 가능한 클래스/객체/확장 레코드 인스턴스:
IEnumerator와IEnumerable인터페이스를 지원하는 구조 타입의 인스턴스예요. 이 경우 제어 변수 타입은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루프의 제어 변수 타입과 일치하는지
열거 가능한 클래스가 실제로 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"
위 코드는 다음 코드와 동일해요.
{$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 루프와 마찬가지로, 루프 안에서 루프 제어 변수의 값을 바꾸는 것(값을 대입하는 것)은 허용되지 않아요.