09.1 같은 일을 여러 번 시키는 반복
같은 출력을 다섯 번 하고 싶다고 printf를 다섯 줄 복사해 붙이는 방식은 금방 한계에 부딪힙니다. 백 번, 천 번이 되면 손으로 다 쓸 수 없고, 반복 횟수가 프로그램 실행 도중에 정해지는 경우에는 미리 쓸 수조차 없습니다. 이럴 때 정해진 횟수만큼 같은 코드를 되풀이해 실행해 주는 장치가 반복문(loop)입니다. C에서 가장 널리 쓰이는 반복문이 for입니다. 몇 번 돌지 그 횟수가 눈에 보이는 상황, 예를 들어 1부터 10까지 세거나 배열의 첫 칸부터 마지막 칸까지 훑는 것처럼 시작과 끝이 분명할 때 for가 잘 맞습니다.
09.2 초기화, 조건, 갱신 세 부분
for의 괄호 안은 세미콜론 두 개로 세 부분으로 나뉩니다. for (초기화; 조건; 갱신) 꼴입니다. 첫째 자리 초기화는 반복 횟수를 세는 변수, 곧 반복 변수를 만들고 시작값을 넣는 곳으로 전체에서 딱 한 번만 실행됩니다. 둘째 자리 조건은 반복을 계속할지 말지 판단하는 곳으로, 이 식이 참(0이 아님)인 동안 몸통을 실행하고 거짓(0)이 되는 순간 반복을 끝냅니다. 셋째 자리 갱신은 몸통을 한 번 다 돌 때마다 반복 변수를 바꾸는 곳으로, 보통 i++로 1씩 늘립니다.
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 5; i++) {
printf("%d ", i);
}
printf("\n");
return 0;
}
i를 1로 시작하고(초기화), i가 5 이하인 동안(조건), 한 바퀴 돌 때마다 i를 1 늘리며(갱신) 그 값을 출력합니다. 실행 결과는 다음과 같습니다.
1 2 3 4 5
09.3 한 바퀴가 도는 순서
![[C 09] 반복문 for](https://img.thenullpage.com/posts/6544/6544_1_39baf7.webp)
세 부분이 도는 차례를 정확히 알아야 결과를 예측할 수 있습니다. 먼저 초기화가 한 번 실행되어 i가 1이 됩니다. 그다음 조건 i <= 5를 검사해 참이면 몸통을 실행합니다. 몸통이 끝나면 갱신 i++가 실행되어 i가 2가 되고, 다시 조건을 검사합니다. 이 조건 검사, 몸통 실행, 갱신의 고리가 반복되다가 i가 6이 되면 6 <= 5가 거짓이 되어 몸통을 건너뛰고 반복을 빠져나옵니다. 즉 조건은 매 바퀴 몸통 앞에서 검사되고, 갱신은 몸통 뒤에서 일어납니다. 초기화는 처음 한 번뿐이라는 점을 함께 기억해 둡니다.
09.4 1부터 10까지 더하기
반복은 단순 출력뿐 아니라 값을 차곡차곡 쌓는 데 자주 쓰입니다. 합을 담을 변수를 반복 밖에서 0으로 만들어 두고, 반복 안에서 그 변수에 i를 더해 나가면 됩니다. sum += i는 sum = sum + i의 줄임 표현으로, 기존 합에 이번 i를 더해 다시 담으라는 뜻입니다.
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
printf("합계: %d\n", sum);
출력은 다음과 같습니다.
합계: 55
주의. 합을 담는 sum을 반복 안에서 0으로 초기화하면 매 바퀴 0으로 지워져 값이 영영 쌓이지 않습니다. 누적 변수의 초기화는 반드시 반복문 바깥, 반복이 시작되기 전에 한 번만 해야 합니다.
09.5 구구단 한 단 출력
반복 변수 i를 출력만 하지 않고 계산식에 넣으면 규칙적인 표가 만들어집니다. 7단을 찍으려면 고정된 단 값 7에, 1부터 9까지 변하는 i를 곱해 줍니다.
int dan = 7;
for (int i = 1; i <= 9; i++) {
printf("%d x %d = %d\n", dan, i, dan * i);
}
출력은 다음과 같습니다.
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
7 x 9 = 63
09.6 거꾸로 세는 카운트다운
시작값을 크게 잡고 갱신에서 i--로 줄이면 역순으로 셀 수 있습니다. 조건도 방향을 뒤집어 i >= 1처럼 하한을 기준으로 잡습니다.
for (int i = 5; i >= 1; i--) {
printf("%d ", i);
}
printf("발사!\n");
5 4 3 2 1 발사!
여기서 조건을 i > 0으로 써도 결과는 같습니다. 하한이 1이므로 i >= 1과 i > 0은 같은 지점에서 멈추기 때문입니다.
09.7 간격을 두고 건너뛰기
갱신 부분이 꼭 1씩 바꿔야 하는 것은 아닙니다. i += 2로 두 칸씩 건너뛰면 0부터 10까지 짝수만 골라 낼 수 있습니다. 증가 폭을 바꾸는 것만으로 원하는 간격을 만드는 셈입니다.
for (int i = 0; i <= 10; i += 2) {
printf("%d ", i);
}
printf("\n");
0 2 4 6 8 10
09.8 배열을 처음부터 끝까지 훑기
for가 가장 빛나는 곳이 배열 순회입니다. 여기서 초보자가 꼭 짚어야 할 것이 있습니다. 배열의 칸 번호(인덱스)는 1이 아니라 0부터 시작합니다. 그래서 원소가 다섯 개인 배열의 유효한 인덱스는 0, 1, 2, 3, 4입니다. 반복 변수도 0에서 출발하고, 조건은 i < 5처럼 개수보다 작을 때까지로 씁니다.
int scores[5] = {90, 85, 70, 100, 60};
for (int i = 0; i < 5; i++) {
printf("%d번 칸: %d\n", i, scores[i]);
}
출력은 다음과 같습니다.
0번 칸: 90
1번 칸: 85
2번 칸: 70
3번 칸: 100
4번 칸: 60
인덱스가 0부터 시작하기 때문에 i < 5는 정확히 다섯 번(0, 1, 2, 3, 4) 돌고, i가 5가 되는 순간 멈춥니다.
09.9 off-by-one과 무한 루프 함정
함정. 방금 배열에서 조건을 i <= 5로 잘못 쓰면 i가 5일 때도 몸통이 돌아 scores[5]를 읽습니다. 유효한 칸은 4번까지인데 존재하지도 않는 5번 칸을 건드리는 것으로, 이렇게 경계를 한 칸 넘거나 못 미치는 실수를 off-by-one 오류라고 부릅니다. 배열 경계를 벗어난 접근은 정의되지 않은 동작(UB, undefined behavior)이라, 운이 나쁘면 엉뚱한 값이 나오거나 프로그램이 그 자리에서 죽습니다. 원소 개수가 n일 때 0부터 시작하는 순회의 조건은 i < n, 이 관용구를 통째로 외워 두는 편이 안전합니다.
함정. 갱신을 빠뜨리거나 조건이 영영 거짓이 되지 않으면 반복이 끝나지 않는 무한 루프에 빠집니다. 예를 들어 for (int i = 1; i >= 1; i++)는 i가 커지기만 해 조건이 늘 참이고, for (int i = 0; i != 10; i += 3)은 i가 0, 3, 6, 9, 12로 건너뛰어 10을 정확히 밟지 못하므로 != 조건이 결코 거짓이 되지 않습니다. 종료 조건은 반복 변수가 반드시 지나가는 값을 기준으로 잡고, 같지 않음(!=)보다 부등호(<, <=)를 쓰는 것이 훨씬 안전합니다.