반복문은 같은 일을 여러 번 되풀이할 때 씁니다. 앞 편의 for는 몇 번 돌지를 미리 아는 반복에 어울렸습니다. 그런데 실제 프로그램에서는 몇 번 돌아야 할지 시작 시점에 알 수 없는 경우가 더 많습니다. 사용자가 언제 그만둘지, 잔액이 언제 바닥날지는 돌려 봐야 압니다.
이렇게 조건이 참인 동안 계속하는 반복에 어울리는 것이 while과 do-while입니다. 이번 편은 이 둘을 예제 위주로 익힙니다. 각각 언제 쓰는지, for와 무엇이 다른지, do-while은 왜 최소 한 번은 실행되는지를 짧은 코드와 그 실행 결과로 하나씩 확인하겠습니다.
10.1 조건이 참인 동안 도는 while
while은 괄호 안의 조건을 검사해서, 참이면 중괄호 안(몸통)을 실행하고 다시 조건으로 돌아옵니다. 조건이 거짓이 되는 순간 반복을 멈춥니다. 1부터 5까지 세어 보겠습니다.
int i = 1;
while (i <= 5) { /* i가 5 이하인 동안 반복 */
printf("%d ", i);
i++; /* 값을 1씩 늘려 조건에 다가감 */
}
printf("\n");
실행하면 이렇게 출력됩니다.
1 2 3 4 5
i가 1, 2, 3, 4, 5일 때 몸통이 돌고, i가 6이 되면 조건 i <= 5가 거짓이라 멈춥니다. 여기서 반드시 눈여겨볼 것은 몸통 안의 i++입니다.
10.2 갱신을 빠뜨리면 멈추지 않는 무한 루프
while이 멈추려면 조건에 쓰인 값이 반복 도중에 변해서 언젠가 거짓이 되어야 합니다. 함정. 조건에 쓰인 값을 몸통에서 갱신하지 않으면, 조건이 영원히 참이라 반복이 끝나지 않습니다. 이것을 무한 루프(infinite loop)라고 합니다.
int i = 1;
while (i <= 5) {
printf("%d ", i); /* i를 늘리는 줄이 빠졌음 */
}
위 코드는 i를 늘리는 i++가 없어 i가 계속 1에 머뭅니다. 조건이 언제나 참이라 화면에 1이 끝없이 찍힙니다. while을 쓸 때는 이 반복을 멈추는 값이 몸통 안에서 실제로 바뀌는가를 늘 확인하십시오. while의 준비물은 셋입니다. 시작값(초기화), 멈춤을 정하는 조건, 그리고 조건을 언젠가 거짓으로 만드는 갱신입니다.
10.3 for와 while은 무엇이 다른가
for와 while은 사실 같은 일을 합니다. for는 시작값과 조건과 갱신을 괄호 한 줄에 모아 두고, while은 그 셋을 따로 흩어 둘 뿐입니다. 아래 두 코드는 완전히 똑같이 동작합니다.
for (int i = 1; i <= 5; i++) {
printf("%d ", i);
}
int i = 1;
while (i <= 5) {
printf("%d ", i);
i++;
}
반복 횟수가 5번으로 정해져 있으니 이 경우는 for가 더 깔끔합니다. 반대로 몇 번 돌지 모르고 조건으로만 멈추는 경우에는 while이 자연스럽습니다. 이어지는 예제들이 바로 그런 상황입니다.
10.4 사용자가 그만둘 때까지 입력받기
사용자가 숫자를 계속 넣다가 0을 넣으면 끝내고, 그때까지의 합을 구한다고 합시다. 몇 개를 넣을지는 미리 알 수 없으니 for로는 어색하고 while이 딱 맞습니다.
int n, sum = 0;
printf("숫자 입력(0이면 끝): ");
scanf("%d", &n);
while (n != 0) { /* 0이 들어오면 멈춤 */
sum += n;
printf("숫자 입력(0이면 끝): ");
scanf("%d", &n); /* 다음 값을 받아 조건을 갱신 */
}
printf("합계: %d\n", sum);
사용자가 10, 20, 5, 0을 차례로 넣으면 이렇게 됩니다.
숫자 입력(0이면 끝): 10
숫자 입력(0이면 끝): 20
숫자 입력(0이면 끝): 5
숫자 입력(0이면 끝): 0
합계: 35
반복을 멈추는 값 n을 몸통 끝에서 scanf로 다시 받는 점이 핵심입니다. 이 입력을 빠뜨리면 n이 처음 값 그대로라 또 무한 루프에 빠집니다. 이렇게 끝을 알리는 약속된 입력값을 보초값(sentinel)이라고 부릅니다.
10.5 숫자의 자릿수 세기
while은 값이 어떤 상태가 될 때까지 깎아 나가는 일에도 잘 맞습니다. 어떤 정수가 몇 자리인지 세어 보겠습니다. 10으로 나누면 맨 뒤 한 자리가 사라지므로, 0이 될 때까지 나누며 그 횟수를 셉니다.
int n = 12345;
int digits = 0;
while (n > 0) {
n /= 10; /* 맨 뒷자리 하나 제거 */
digits++; /* 자릿수 하나 셈 */
}
printf("자릿수: %d\n", digits);
결과는 다음과 같습니다.
자릿수: 5
n이 12345에서 1234, 123, 12, 1을 거쳐 0이 되기까지 다섯 번 나뉘며 digits가 5가 됩니다. 여기서도 n /= 10이 조건 n > 0을 향해 값을 움직여 주므로 반복이 제대로 끝납니다.
10.6 먼저 실행하고 나중에 조건을 묻는 do-while
while은 조건을 먼저 검사하므로, 처음부터 조건이 거짓이면 몸통이 한 번도 실행되지 않습니다. 그런데 일단 한 번은 하고 나서 계속할지 정하고 싶은 경우가 있습니다. 이럴 때 쓰는 것이 do-while입니다. 몸통을 먼저 실행하고, 그다음에 조건을 검사합니다.
int count = 10;
do {
printf("한 번은 실행됩니다 (count=%d)\n", count);
} while (count < 5); /* 조건이 거짓이라 더는 안 돎 */
한 번은 실행됩니다 (count=10)
count가 10이라 조건 count < 5는 처음부터 거짓입니다. 그래도 do-while은 몸통을 먼저 실행한 뒤에 조건을 보므로, 안내 문구가 정확히 한 번 출력됩니다. 같은 조건을 while로 썼다면 한 줄도 찍히지 않습니다. 주의. do-while은 맨 끝에 세미콜론을 반드시 붙입니다. while(...) 뒤의 이 세미콜론을 빠뜨리는 실수가 잦습니다.
10.7 잘못 누르면 다시 묻는 메뉴 입력
do-while이 가장 빛나는 곳은 최소 한 번은 물어보고, 올바른 값이 나올 때까지 다시 묻는 입력 검사입니다. 메뉴 번호를 1에서 3 사이로만 받고 싶다고 합시다.
int menu;
do {
printf("메뉴 선택(1~3): ");
scanf("%d", &menu);
} while (menu < 1 || menu > 3); /* 범위 밖이면 다시 */
printf("선택한 메뉴: %d\n", menu);
사용자가 7, 0, 2를 차례로 넣으면 이렇게 됩니다.
메뉴 선택(1~3): 7
메뉴 선택(1~3): 0
메뉴 선택(1~3): 2
선택한 메뉴: 2
입력을 받는 동작 자체는 무조건 한 번은 있어야 하므로, 몸통을 먼저 실행하는 do-while이 자연스럽습니다. 잘못된 값 7과 0에는 조건이 참이라 다시 묻고, 올바른 값 2가 들어오면 조건이 거짓이 되어 빠져나옵니다. 이 재입력 패턴은 do-while의 대표적인 쓰임입니다.
10.8 while과 do-while, 언제 무엇을 쓰나
![[C 10] 반복문 while과 do-while](https://img.thenullpage.com/posts/6545/6545_1_112105.webp)
while은 조건을 먼저 검사하므로 조건이 처음부터 거짓이면 아예 한 번도 안 할 수 있는 반복에 씁니다. 잔액이 남아 있는 동안 출금하기, 0이 들어올 때까지 입력받기가 그렇습니다. do-while은 조건을 나중에 검사하므로 무슨 일이 있어도 최소 한 번은 하는 반복, 특히 한 번 보여 주고 다시 물어보는 입력 검사에 씁니다. for는 이 둘과 겹치지만 횟수가 정해진 반복에서 가장 읽기 좋습니다. 셋 다 공통으로, 반복을 멈추는 값이 몸통 안에서 실제로 변하는지만 놓치지 않으면 무한 루프라는 함정을 피할 수 있습니다.