Object Pascal · 기본
Object Pascal 첫걸음
조건과 반복 - begin end 블록
if then else 와 세미콜론 함정, case 와 범위, for to·downto, while do, repeat until, Break·Continue
개발자KR · 원고 갱신
이 장에서 배우는 것
지금까지 만든 프로그램은 위에서 아래로 한 줄씩 차례대로 실행되고 끝났다. 카페 계산대는 그렇게 일하지 않는다. 재고가 모자라면 주문을 거절해야 하고, 시간대에 따라 할인율이 달라지며, 마감 때까지 같은 계산을 되풀이해야 한다. 이 장에서는 프로그램이 상황에 따라 갈라지고(조건문) 같은 일을 되풀이하게(반복문) 하는 문장들을 배운다. 모두 begin과 end로 문장을 묶는 규칙 위에서 움직인다.
if then else를 쓰고,else앞에 세미콜론을 붙이면 왜 컴파일 오류가 나는지 설명할 수 있다.case문에서 값 목록과 범위(7..10)를 쓰고else갈래를 둘 수 있다.for(to,downto),while,repeat until의 차이를 알고 상황에 맞게 고른다.Break와Continue로 반복 중간에 빠져나가거나 한 차례를 건너뛴다.
문제 상황
동네 카페 계산대 프로그램에 다음 요구가 들어왔다. 메뉴 번호로 메뉴 이름과 가격을 찾는다. 주문 수량이 재고보다 많으면 거절한다. 시간대마다 할인 안내를 다르게 보여 준다. 마감 전에 주문을 번호순으로 훑으며 취소된 주문은 건너뛰고, 누적 금액이 한도에 닿으면 처리를 멈춘다.
앞 장에서 만든 변수와 타입만으로는 이 일을 할 수 없다. 변수에 값을 넣고 출력하는 것뿐이어서, 같은 줄을 복사해 붙이거나 모든 경우를 한꺼번에 실행해 버리게 된다. 값을 보고 길을 고르는 문장과 같은 구간을 되풀이하는 문장이 필요하다.
if then else 와 세미콜론 함정
기본 형태
조건문은 조건식이 참(True)인지 거짓(False)인지에 따라 실행할 문장을 고른다. 조건식은 qty > stock처럼 참 거짓 하나로 결정되는 식이며, 이런 값의 타입을 불리언(Boolean)이라 한다. 괄호도 then도 생략할 수 없다. C 계열 언어와 달리 조건식에 괄호를 씌울 필요는 없고 then이 그 자리를 대신한다.
if qty > stock then
WriteLn('재고 부족')
else
WriteLn('주문 가능');
같은 같음 비교는 = 하나다. 대입은 :=이므로 둘이 헷갈릴 일은 적다. 다른지 비교할 때는 <>를 쓴다.
세미콜론은 문장을 끝낸다
Pascal에서 세미콜론은 문장 사이를 구분하는 기호다. if ... then A else B는 문장 하나이므로, 그 한가운데인 A와 else 사이에 세미콜론이 들어가면 if 문이 거기서 끝난 것으로 읽힌다. 그러면 else는 짝 없이 혼자 남고 컴파일러는 문법 오류를 낸다.
오류 메시지가 else를 가리키는데 원인은 한 줄 위의 세미콜론이라서 처음에는 찾기 어렵다. 규칙은 단순하다. else 바로 앞 문장에는 세미콜론을 붙이지 않고, 전체 if 문이 끝나는 자리에만 붙인다.
여러 문장은 begin end 로 묶는다
then과 else 뒤에는 문장이 하나만 올 수 있다. 문장이 둘 이상이면 begin과 end로 묶어 복합문(compound statement) 하나로 만든다. 들여쓰기는 사람을 위한 표시일 뿐이고, 컴파일러는 들여쓰기를 보지 않는다.
if qty > stock then
WriteLn('재고 부족');
WriteLn('다른 메뉴를 권한다'); // 항상 실행된다
위 코드는 두 번째 줄이 if 밖에 있어서 재고와 상관없이 늘 실행된다. 묶음 안의 마지막 문장 뒤 세미콜론은 있어도 없어도 되지만, 이 책은 있는 쪽으로 통일한다. 단 end 바로 뒤에 else가 올 때는 end 뒤에 세미콜론을 붙이지 않는다.
else if 사슬
갈래가 셋 이상이면 else 뒤에 새 if를 이어 붙인다. 위에서부터 검사해 처음 참인 갈래 하나만 실행하고 나머지는 건너뛴다. 아래 완성 코드의 재고 확인 부분이 이 형태다.
case 문과 범위
하나의 값을 여러 후보와 견줄 때는 else if를 길게 이어 쓰는 대신 case를 쓴다. case 뒤의 식은 순서형(ordinal) 값이어야 한다. 정수나 문자, 열거 값처럼 앞뒤 순서가 정해진 타입을 말한다. 소수는 쓸 수 없다. 각 갈래의 라벨은 상수여야 하고, 쉼표로 여러 값을 나열하거나 ..으로 범위를 쓸 수 있다.
case hour of
7..10: period := '아침 할인 10%';
11..14: period := '점심 정가';
else
period := '그 밖의 시간';
end;
어느 라벨에도 맞지 않을 때의 갈래는 else다. case는 end로 닫으며, 이 end에는 짝이 되는 begin이 없다. 갈래 하나에 문장이 여럿이면 갈래 안에서 다시 begin ... end를 쓴다. 라벨이 서로 겹치면 컴파일 오류가 난다. 한편 case의 else 앞 세미콜론은 허용된다. if와 달리 case의 else는 앞 문장과 이어진 문장이 아니라 갈래 목록의 마지막 항목이기 때문이다.
Delphi와 다른 점이 하나 있다. Free Pascal은 case에 문자열을 쓰는 것도 받아 주지만 Delphi는 받아 주지 않는다. 이식성을 위해 이 책은 순서형만 쓴다.
반복문 네 가지
for: 횟수가 정해진 반복
for 변수 := 시작 to 끝 do 문장은 변수를 시작값부터 끝값까지 1씩 늘리며 문장을 실행한다. 거꾸로 세려면 downto를 쓴다. 증가량을 바꾸는 문법은 없다. 시작값이 끝값보다 크면 to 반복은 한 번도 실행되지 않는다. 몇 가지 규칙을 기억해 두자.
- 반복 변수는 정수 같은 순서형이어야 한다. 소수 변수는 쓸 수 없다.
- 시작값과 끝값은 반복이 시작될 때 한 번만 계산된다.
- 반복 안에서 반복 변수에 값을 대입하면 컴파일 오류다.
- 반복이 끝난 뒤 반복 변수의 값은 정해져 있지 않으므로 읽지 않는다.
while 과 repeat
횟수를 미리 모를 때는 조건으로 반복한다. while 조건 do는 조건이 참인 동안 되풀이하며 검사를 앞에서 한다. 처음부터 거짓이면 한 번도 실행하지 않는다. repeat ... until 조건은 본문을 먼저 실행하고 뒤에서 검사하므로 최소 한 번은 실행되고, 조건이 참이 되면 멈춘다. 멈추는 조건의 방향이 while과 반대라는 점이 중요하다. repeat는 본문이 여러 문장이어도 repeat와 until 자체가 경계 역할을 하므로 begin end가 필요 없다.
| 문장 | 검사 시점 | 최소 실행 | 어울리는 경우 |
|---|---|---|---|
| for ... to / downto | 횟수가 시작 때 정해짐 | 0회 | 번호 1~5처럼 범위가 분명할 때 |
| while ... do | 본문 앞 | 0회 | 조건이 처음부터 거짓일 수 있을 때 |
| repeat ... until | 본문 뒤 | 1회 | 일단 한 번 하고 결과를 보고 판단할 때 |
Break 와 Continue
Break는 가장 안쪽 반복을 즉시 끝내고 반복문 다음 문장으로 넘어간다. Continue는 이번 차례의 나머지 문장을 건너뛰고 다음 차례로 간다. for에서는 다음 값으로, while에서는 조건 검사로, repeat에서는 until 검사로 넘어간다. 둘 다 반복문 안에서만 쓸 수 있다.
두 문장은 반복 안의 if와 함께 쓰는 것이 보통이다. 조건에 맞지 않는 항목은 Continue로 걸러 내고, 더 처리할 이유가 없어지면 Break로 나간다. 반복이 겹쳐 있으면 안쪽 반복에만 영향을 준다.
완성 코드
다음 코드는 앞에서 설명한 문장을 카페 계산대 상황에 하나씩 적용한다. 한 파일 main.pas이며 입력을 읽지 않는다.
{$mode objfpc}{$H+}
program Main;
var
code, price: Integer;
itemName, period: string;
stock, qty, sold: Integer;
hour, i, orderNo, sum: Integer;
begin
WriteLn('== 메뉴표 ==');
for code := 1 to 5 do
begin
case code of
1:
begin
itemName := '아메리카노';
price := 3000;
end;
2:
begin
itemName := '카페라테';
price := 3500;
end;
3:
begin
itemName := '바닐라라테';
price := 4000;
end;
4, 5:
begin
itemName := '디저트';
price := 4500;
end;
else
begin
itemName := '없음';
price := 0;
end;
end;
WriteLn(code, '번 ', itemName, ' ', price, '원');
end;
WriteLn('== 재고 확인 ==');
stock := 10;
qty := 4;
if qty <= 0 then
WriteLn('수량이 올바르지 않다')
else if qty > stock then
WriteLn('재고 부족: 남은 ', stock, '잔')
else
begin
stock := stock - qty;
WriteLn('주문 접수: ', qty, '잔, 남은 재고 ', stock, '잔');
end;
WriteLn('== 시간대 ==');
hour := 7;
while hour <= 21 do
begin
case hour of
7..10: period := '아침 할인 10%';
11..14: period := '점심 정가';
15..17: period := '오후 할인 5%';
else
period := '저녁 정가';
end;
WriteLn(hour, '시: ', period);
hour := hour + 4;
end;
WriteLn('== 마감 카운트다운 ==');
Write('마감까지: ');
for i := 5 downto 1 do
Write(i, ' ');
WriteLn('종료');
WriteLn('== 세트 판매 ==');
stock := 10;
sold := 0;
repeat
stock := stock - 3;
sold := sold + 1;
WriteLn('세트 판매 ', sold, '회, 재고 ', stock);
until stock < 3;
WriteLn('== 주문 처리 ==');
sum := 0;
for orderNo := 1 to 10 do
begin
if orderNo mod 3 = 0 then
begin
WriteLn('주문 ', orderNo, ' 취소, 건너뜀');
Continue;
end;
sum := sum + 2500;
WriteLn('주문 ', orderNo, ' 합계 ', sum);
if sum >= 15000 then
begin
WriteLn('한도 도달, 반복 중단');
Break;
end;
end;
WriteLn('최종 합계 ', sum, '원');
end.
줄별 해설
첫 두 줄. 모든 파일은 {$mode objfpc}{$H+}로 시작한다. 앞의 지시어는 Delphi와 비슷한 문법 모드를 켜고, {$H+}는 string을 길이 제한 없는 긴 문자열로 만든다. 그 아래에 program Main;이 온다.
var 부분. 쓰는 변수를 모두 앞에 선언했다. 앞 장에서 배운 대로 Pascal은 선언을 먼저 하는 언어다.
메뉴표. for가 1번부터 5번까지 돌며 case code of로 갈래를 고른다. 각 갈래가 문장 둘(이름과 가격)을 실행하므로 begin end로 묶었다. 4, 5:는 두 값이 같은 갈래를 쓴다는 뜻이다. else는 1~5 밖의 값에 대비한 안전장치이며, 이 반복에서는 실행되지 않는다. 출력 줄의 WriteLn은 쉼표로 숫자와 문자열을 이어 한 줄에 쓴다.
재고 확인. else if 사슬이다. 수량이 0 이하인 경우, 재고보다 많은 경우를 차례로 걸러 내고 마지막 else에서 재고를 줄인다. 첫 두 갈래의 WriteLn 뒤에는 세미콜론이 없다. 뒤에 else가 이어지기 때문이다. 마지막 갈래는 문장이 둘이라 begin end를 썼다.
시간대. while이 7시부터 4시간씩 건너뛰며 7, 11, 15, 19시를 처리한다. for는 증가량을 못 바꾸므로 이런 경우에 while을 쓴다. 반복 끝의 hour := hour + 4를 빠뜨리면 조건이 영원히 참이어서 프로그램이 끝나지 않는다. case의 7..10:은 범위 라벨이고, 19시는 어느 라벨에도 맞지 않아 else로 간다.
마감 카운트다운. Write는 줄바꿈 없이 출력한다. downto로 5에서 1까지 내려가며 숫자와 공백을 이어 쓰고, 마지막에 WriteLn으로 줄을 바꾼다. 이 for는 본문이 한 문장이라 begin end가 없다.
세트 판매. 재고 10잔에서 세트 하나(3잔)씩 팔고, 남은 재고가 3 미만이 되면 멈춘다. until의 조건은 멈추는 조건이다. 판매 3회째에 재고가 1이 되어 반복이 끝난다.
주문 처리. 주문 번호가 3의 배수이면 취소로 보고 Continue로 합계 계산을 건너뛴다. 그렇지 않으면 2500원을 더하고, 합계가 15000원 이상이면 Break로 반복을 끝낸다. 8번 주문에서 합계가 15000이 되어 9번과 10번은 처리되지 않는다. 반복 변수 orderNo는 반복이 끝난 뒤 읽지 않고, 누적값 sum만 읽는다. orderNo mod 3 = 0에서 mod는 나머지를 구하는 연산자이고 =보다 먼저 계산된다.
실행 결과
$ fpc main.pas
$ ./main
== 메뉴표 ==
1번 아메리카노 3000원
2번 카페라테 3500원
3번 바닐라라테 4000원
4번 디저트 4500원
5번 디저트 4500원
== 재고 확인 ==
주문 접수: 4잔, 남은 재고 6잔
== 시간대 ==
7시: 아침 할인 10%
11시: 점심 정가
15시: 오후 할인 5%
19시: 저녁 정가
== 마감 카운트다운 ==
마감까지: 5 4 3 2 1 종료
== 세트 판매 ==
세트 판매 1회, 재고 7
세트 판매 2회, 재고 4
세트 판매 3회, 재고 1
== 주문 처리 ==
주문 1 합계 2500
주문 2 합계 5000
주문 3 취소, 건너뜀
주문 4 합계 7500
주문 5 합계 10000
주문 6 취소, 건너뜀
주문 7 합계 12500
주문 8 합계 15000
한도 도달, 반복 중단
최종 합계 15000원
실무에서 자주 틀리는 것
else 앞에 세미콜론을 붙인다
가장 흔한 실수다. 줄을 끝낼 때마다 세미콜론을 붙이는 습관이 원인이다.
// 틀림: 컴파일 오류
if qty > stock then
WriteLn('재고 부족');
else
WriteLn('주문 가능');
// 고침
if qty > stock then
WriteLn('재고 부족')
else
WriteLn('주문 가능');
begin end 없이 문장을 둘 이상 쓴다
들여쓰기를 믿고 두 번째 문장도 if에 속한다고 생각하는 경우다. 컴파일은 되지만 동작이 틀린다.
// 틀림: 재고 차감이 조건과 상관없이 실행된다
if qty <= stock then
WriteLn('주문 접수');
stock := stock - qty;
// 고침
if qty <= stock then
begin
WriteLn('주문 접수');
stock := stock - qty;
end;
and, or 를 괄호 없이 쓴다
Pascal에서 and와 or는 비교 연산자(<=, = 등)보다 먼저 계산된다. 비교를 괄호로 감싸지 않으면 10 and hour가 먼저 계산되어 타입이 맞지 않는다는 컴파일 오류가 난다.
// 틀림: 컴파일 오류
if hour >= 7 and hour <= 10 then
WriteLn('아침');
// 고침: 비교마다 괄호
if (hour >= 7) and (hour <= 10) then
WriteLn('아침');
반복 조건의 방향과 갱신을 잊는다
while은 참인 동안, repeat는 참이 될 때까지 반복한다. 조건을 반대로 쓰면 한 번도 안 돌거나 끝나지 않는다. 또 while 안에서 조건에 쓰는 변수를 바꾸지 않으면 반복이 끝나지 않는다.
// 틀림: hour 가 바뀌지 않아 끝나지 않는다
while hour <= 21 do
WriteLn(hour, '시');
// 고침
while hour <= 21 do
begin
WriteLn(hour, '시');
hour := hour + 4;
end;
그 밖에 repeat는 본문을 먼저 실행하므로 재고가 처음부터 3 미만이면 재고가 음수가 된다. 처음부터 조건이 거짓일 수 있는 경우에는 while을 쓴다.
한눈에 보기
| 문장 | 모양 | 주의 |
|---|---|---|
| if | if 조건 then A else B; | else 앞에 세미콜론 금지, 여러 문장은 begin end |
| case | case 값 of 라벨: 문장; else 문장; end; | 순서형만, 범위 7..10과 목록 4, 5 가능, 끝의 end 는 짝이 없다 |
| for | for i := a to b do / downto | 증가량 고정, 반복 변수 대입 금지, 끝난 뒤 값 읽지 않기 |
| while | while 조건 do | 참인 동안 반복, 0회 가능, 조건 변수 갱신 필요 |
| repeat | repeat ... until 조건; | 참이 되면 멈춤, 최소 1회, begin end 불필요 |
| Break, Continue | 반복 안의 if 와 함께 | 가장 안쪽 반복에만 적용 |
연습 문제
- 다음 코드는 컴파일되지 않는다. 오류의 원인을 밝히고 고쳐라.
if sum >= 15000 then WriteLn('한도 도달'); else WriteLn('계속'); - 정수 변수
n이 주문 잔 수일 때 1~2잔이면 '소량', 3~9잔이면 '보통', 그 밖이면 '단체'를 출력하는case문을 써라.n이 0이면 어떻게 되는지도 답하라. for로 1잔부터 5잔까지 "N잔 금액원" 표(한 잔 3000원)를 출력하고, 이어서downto로 5에서 1까지 잔 수만 한 줄에 출력하라.- 재고
stock이 0일 때repeat stock := stock - 3; until stock < 3;와while stock >= 3 do stock := stock - 3;의 결과가 어떻게 다른지 설명하라.
정답과 해설
- 원인은
WriteLn('한도 도달');끝의 세미콜론이다. 이 세미콜론이if문을 끝내else가 짝을 잃는다. 세미콜론을 지우면 된다.if sum >= 15000 then WriteLn('한도 도달') else WriteLn('계속'); - 범위 라벨을 쓰고 나머지는
else로 받는다.case n of 1..2: WriteLn('소량'); 3..9: WriteLn('보통'); else WriteLn('단체'); end;n이 0이면 어느 라벨에도 맞지 않아else로 가서 '단체'가 출력된다. 0이나 음수는case앞에서if로 걸러야 한다. -
첫 반복은 1잔 3000원부터 5잔 15000원까지 다섯 줄을 출력하고, 둘째 반복은for n := 1 to 5 do WriteLn(n, '잔 ', n * 3000, '원'); for n := 5 downto 1 do Write(n, ' '); WriteLn;5 4 3 2 1을 한 줄에 출력한다. 마지막WriteLn;은 줄만 바꾼다. repeat는 본문을 먼저 실행하므로stock이 0에서 -3이 되고, 그 뒤에 조건을 검사한다.-3 < 3은 참이니 한 번 돌고 끝나며 재고는 음수로 남는다.while은0 >= 3이 거짓이므로 본문을 한 번도 실행하지 않고 재고는 0 그대로다. 처음부터 조건이 거짓일 수 있으면while이 맞다.
이 장의 문장들은 지금은 한 프로그램 안에 길게 늘어놓았다. 다음 장에서는 이런 덩어리에 이름을 붙여 프로시저와 함수로 나누고, 값을 넘겨 주는 방식을 배운다. 문법 세부는 Free Pascal 언어 레퍼런스에서 확인할 수 있다.
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.