2단원 — 반복문
같은 작업을 여러 번 수행하는 도구가 **반복문**입니다. C에는 세 가지 반복문이 있고, 각각 어울리는 상황이 다릅니다.
이 강의에서 배우는 것
- 1`for`, `while`, `do-while`을 상황에 맞게 선택한다.
- 2중첩 반복문으로 2차원 패턴을 만든다.
- 3`break`, `continue`로 흐름을 미세 제어한다.
- 4무한 루프와 오프바이원(off-by-one) 오류를 피한다.
왜 세 가지나 필요할까?
세 반복문은 **언제 조건을 검사하느냐** 만 다릅니다.
for (init; cond; step) { body } ← 초기화·검사·증분이 한 줄, 횟수 명확할 때
while (cond) { body } ← 검사 먼저, 0회 실행 가능
do { body } while (cond); ← 본문 먼저, 최소 1회 실행 보장세 가지 모두 다른 두 가지로 옮겨 쓸 수 있지만, **의도를 가장 잘 드러내는** 형태를 고르는 것이 좋습니다.
핵심 개념
1) `for` — 횟수가 명확할 때
for (int i = 0; i < 10; i++) {
/* ... */
}세 부분(`초기화 ; 조건 ; 증분`)이 한 줄에 모여 있어 "**i가 0부터 9까지**"가 한눈에 보입니다.
2) `while` — 조건만 있을 때
while (n > 0) {
sum += n % 10;
n /= 10;
}"조건이 만족되는 동안" 반복. **0회도 가능**합니다.
3) `do-while` — 본문 먼저, 검사 나중
do {
print_menu();
scanf("%d", &choice);
} while (choice != 0);본문이 **항상 한 번은** 실행됩니다. 메뉴 입력처럼 "최소 한 번은 묻기"에 적합.
4) `break` / `continue`
- `break`: 가장 가까운 반복문을 즉시 종료.
- `continue`: 본문의 나머지를 건너뛰고 다음 반복(증분/조건 평가)으로.
for (int i = 1; i <= 20; i++) {
if (i % 2) continue; // 홀수는 건너뜀
if (i == 14) break; // 14에서 종료
printf("%d ", i);
}예제로 보기
예제 1 — `ex01_for.c` : 1부터 N까지의 합
long sum = 0;
for (int i = 1; i <= n; i++) sum += i;**입력**: `10`
N 입력: 1부터 10까지의 합: 55핵심: `i <= n`을 `i < n`으로 잘못 적으면 1+...+9=45가 됩니다 (오프바이원).
예제 2 — `ex02_while.c` : 자릿수 합
while (n > 0) {
sum += n % 10;
n /= 10;
}**입력**: `1234`
정수 입력: 1234의 자릿수 합: 10핵심: 자릿수 처리는 **`%`로 끝자리, `/`로 자릿수 줄이기**가 정석 패턴.
예제 3 — `ex03_do_while.c` : 메뉴 입력 (최소 1회)
본문 → 조건 검사 순서이므로 종료 조건(0)을 입력해도 메뉴는 한 번 보입니다.
예제 4 — `ex04_nested.c` : 구구단 (중첩 for)
for (int i = 2; i <= 9; i++)
for (int j = 1; j <= 9; j++)
printf("%d x %d = %2d\n", i, j, i * j);**실행 결과 (앞부분)**
=== 2단 ===
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
2 x 4 = 8
2 x 5 = 10
2 x 6 = 12
2 x 7 = 14
2 x 8 = 16
2 x 9 = 18
=== 3단 ===
3 x 1 = 3
...핵심: 바깥 루프가 한 번 돌 때 안쪽 루프가 **전부** 돕니다.
예제 5 — `ex05_break_continue.c` : 흐름 제어
1~20 중 짝수만 출력하다가 14에서 종료.
**실행 결과**
2 4 6 8 10 12핵심: `continue`는 "건너뛰기", `break`는 "탈출".
다른 시각으로 보기 — 어떤 반복문을 고를까?
횟수가 미리 정해진다 → for
조건만 있고, 0회도 가능 → while
본문을 최소 1번은 돌려야 → do-while세 가지 모두 다음처럼 동치 변환이 가능합니다.
/* for */ /* while */
for (int i = 0; i < n; i++) int i = 0;
body; while (i < n) {
body;
i++;
}자주 하는 실수
- **오프바이원(off-by-one)**: `i <= n` vs `i < n`을 헷갈려 1번 더/덜 도는 버그.
- **무한 루프**: `for (int i = 0; i < n; )` 처럼 증분을 잊으면 영원히 반복.
- **`while (n--)` 의 효과**: 0이 되면 false지만 `n--`은 한 번 더 평가됩니다. 의도를 점검.
- **반복 안에서 변수 재선언**: `for (int i = ...)`의 `i`는 루프 종료 후 사라집니다.
- **중첩 break**: `break`는 한 단계만 빠져나옵니다. 두 단계 빠지려면 플래그 변수나 함수 반환 사용.
정리
- 세 반복문은 검사 시점만 다르며, 상황에 맞는 의도를 표현한다.
- 횟수가 명확하면 `for`, 조건뿐이면 `while`, 최소 1회면 `do-while`.
- `break`/`continue`로 미세 제어가 가능하지만 과용하면 가독성이 떨어진다.
- 중첩 반복은 안쪽 루프가 바깥 한 번에 대해 전체 회전한다.
- 종료 조건을 명확히 적어 무한 루프를 만들지 말 것.
직접 해 보기
cd src
gcc -std=c11 -Wall -o ex01 ex01_for.c && echo 10 | ./ex01
gcc -std=c11 -Wall -o ex02 ex02_while.c && echo 1234 | ./ex02
gcc -std=c11 -Wall -o ex04 ex04_nested.c && ./ex04 | head -20
gcc -std=c11 -Wall -o ex05 ex05_break_continue.c && ./ex05응용:
- `ex01`을 `while`과 `do-while`로 각각 다시 작성해 보세요.
- `ex04`에서 안쪽 루프를 `j <= i`로 바꾸면 출력은 어떻게 달라질까요?
💻 예제 (examples)
실제로 컴파일·실행해 결과를 확인할 수 있는 예제입니다.
#include <stdio.h>
int main(void) {
int n;
printf("N 입력: ");
scanf("%d", &n);
long sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
printf("1부터 %d까지의 합: %ld\n", n, sum);
return 0;
}
#include <stdio.h>
int main(void) {
int n;
printf("정수 입력: ");
scanf("%d", &n);
int original = n;
int sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
printf("%d의 자릿수 합: %d\n", original, sum);
return 0;
}
#include <stdio.h>
int main(void) {
int choice;
do {
printf("\n=== 메뉴 ===\n");
printf("1. 인사\n");
printf("2. 시간\n");
printf("0. 종료\n");
printf("선택: ");
if (scanf("%d", &choice) != 1) break;
switch (choice) {
case 1: printf("안녕하세요!\n"); break;
case 2: printf("지금은 학습 시간입니다.\n"); break;
case 0: printf("종료합니다.\n"); break;
default: printf("잘못된 선택\n");
}
} while (choice != 0);
return 0;
}
#include <stdio.h>
int main(void) {
for (int i = 2; i <= 9; i++) {
printf("=== %d단 ===\n", i);
for (int j = 1; j <= 9; j++) {
printf("%d x %d = %2d\n", i, j, i * j);
}
}
return 0;
}
#include <stdio.h>
int main(void) {
/* 1~20 중 짝수만 출력하다가 14를 만나면 종료 */
for (int i = 1; i <= 20; i++) {
if (i % 2 != 0) continue; // 홀수는 건너뜀
if (i == 14) break; // 14에서 종료
printf("%d ", i);
}
printf("\n");
return 0;
}
2 4 6 8 10 12📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
문제 1 (hw01.c)
목표: 입력 N에 대해 N!(팩토리얼)을 출력하세요. 단, 결과는 `long long` 타입.
- 파일명: hw01.c
N 입력: 10
10! = 3628800▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
int n;
printf("N 입력: ");
scanf("%d", &n);
long long fact = 1;
for (int i = 2; i <= n; i++) {
fact *= i;
}
printf("%d! = %lld\n", n, fact);
return 0;
}
N 입력: 10
10! = 3628800문제 2 (hw02.c)
목표: N을 입력받아 별로 다음과 같은 직각삼각형을 그리세요. (N=4 예시)
- 파일명: hw02.c
*
**
***
****▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
int n;
printf("N 입력: ");
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) putchar('*');
putchar('\n');
}
return 0;
}
*
**
***
****문제 3 (hw03.c)
목표: 2~100 사이의 **소수**를 모두 출력하세요. (한 줄에 공백으로 구분)
- 파일명: hw03.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
for (int n = 2; n <= 100; n++) {
int prime = 1;
for (int d = 2; d * d <= n; d++) {
if (n % d == 0) { prime = 0; break; }
}
if (prime) printf("%d ", n);
}
putchar('\n');
return 0;
}