while과 do-while 반복문
while과 do-while의 조건 검사 시점을 비교하고 횟수를 모르는 반복과 입력 검증 흐름을 안전하게 작성합니다.
사용자가 올바른 값을 입력할 때까지 다시 묻거나, 입력이 끝날 때까지 명령을 읽는 일은 몇 번 반복할지 미리 알 수 없습니다. 이런 반복에는 반복 변수의 규칙을 한 줄에 모으는 for보다, 조건만 적는 while과 do-while이 잘 맞습니다.
두 반복문은 조건이 참인 동안 본문을 되풀이한다는 점은 같지만, 아래 그림처럼 조건을 검사하는 시점이 다릅니다. while은 본문보다 먼저 검사하므로 본문을 한 번도 실행하지 않을 수 있고, do-while은 본문을 먼저 실행한 뒤 검사하므로 본문을 적어도 한 번 실행합니다.
PRE-TEST · POST-TEST
while은 먼저 검사하고 do-while은 먼저 실행한다
둘 다 검사 결과가 true이면 반복하고 false이면 빠져나옵니다. 갈리는 지점은 첫 검사보다 본문이 앞에 있는지입니다.
while (조건) { 본문 }
본문 전에 검사하는 선검사 반복
-
조건 검사
조건 값을
bool로 문맥 변환합니다. 처음부터false면 본문을 건너뜁니다. -
true일 때 본문 실행본문의 끝에 도달하면 다음 조건 검사로 되돌아갑니다.
-
false면 종료while다음 문장으로 제어가 이어집니다.
최소 실행 횟수: 0회
do { 본문 } while (식);
본문 뒤에 검사하는 후검사 반복
-
본문 먼저 실행
진입할 때는 검사하지 않으므로 본문이 반드시 한 번 실행됩니다.
-
식 검사
본문 실행 뒤 식을
bool로 문맥 변환합니다. -
true면 반복,false면 종료반복하면 다시 본문부터 실행하고, 끝나면 다음 문장으로 이어집니다.
최소 실행 횟수: 1회
continue는 가장 안쪽 반복문의 계속 지점으로 이동합니다. while은 앞의 조건 검사, do-while은 본문 뒤의 식 검사입니다. break는 가장 안쪽 반복문 또는 switch 하나를 끝냅니다.while 문
while 문은 조건을 검사해 참이면 본문을 실행하고, 다시 조건으로 돌아갑니다. 조건이 거짓이 되면 본문을 건너뛰고 다음 문장으로 갑니다.
while (조건) {
// 조건이 true인 동안 되풀이할 본문
}while에는 for의 초기화와 반복식 자리가 없습니다. 그래서 조건에 쓰는 변수는 루프 앞에서 준비하고, 조건이 언젠가 거짓이 되도록 본문에서 바꾸어야 합니다. 다음 프로그램은 앞 글의 1부터 5까지 출력을 while로 바꾼 것입니다.
#include <iostream>
int main() {
int i{1};
while (i <= 5) {
std::cout << i << ' ';
++i;
}
std::cout << '\n';
}1 2 3 4 5i의 준비는 루프 앞에, 다음 값으로 넘어가는 ++i는 본문의 끝에 있습니다. 이 프로그램은 for (int i = 1; i <= 5; ++i)와 같은 일을 하며, 루프가 끝난 뒤에도 i가 남아 있어 그 값 6을 쓸 수 있다는 점만 다릅니다. 반복 횟수가 정해진 일에는 세 부분이 한곳에 모이는 for가 읽기 쉽고, 끝나는 시점을 조건으로만 말할 수 있는 일에는 while이 잘 맞습니다.
입력이 끝날 때까지 읽기
while이 가장 자주 쓰이는 곳은 입력을 읽는 반복입니다. std::cin >> command는 읽기에 성공하면 참으로, 입력이 끝났거나 읽기에 실패하면 거짓으로 해석되므로 그대로 조건에 넣을 수 있습니다.
#include <iostream>
#include <string>
int main() {
std::string command;
while (std::cin >> command) {
if (command == "exit") {
break;
}
if (command == "hello") {
std::cout << "hello\n";
} else {
std::cout << "unknown\n";
}
}
std::cout << "done\n";
}입력이 hello abc exit이면 출력은 다음과 같습니다. 입력한 단어는 셋인데, 마지막 done을 빼면 출력은 두 줄뿐이라는 점을 보세요.
hello
unknown
done>>는 공백으로 구분한 단어를 하나씩 읽으므로 hello, abc, exit이 차례로 command에 들어갑니다. exit을 읽으면 break로 루프를 빠져나와 done을 출력합니다. exit 없이 hello만 입력하고 입력을 끝내면, 다음 읽기가 실패해 조건이 거짓이 되므로 역시 hello와 done을 출력합니다. 입력의 끝은 Windows 콘솔에서는 줄 처음에 Ctrl+Z와 Enter로, macOS와 Linux의 터미널에서는 Ctrl+D로 알립니다.
올바른 값을 받을 때까지 다시 묻기
정수를 읽다가 abc처럼 정수가 아닌 입력을 만나면 스트림에 실패 상태(failbit)가 설정되고, 그 뒤의 읽기는 모두 즉시 실패합니다. C++11부터는 이때 변수에 0이 저장됩니다. 다시 읽으려면 clear()로 실패 상태를 지우고, ignore()로 읽지 못한 글자를 버려야 합니다.
#include <iostream>
#include <limits>
int main() {
int age{};
std::cout << "age (1-120): ";
while (!(std::cin >> age) || age < 1 || age > 120) {
if (std::cin.eof()) {
std::cerr << "input ended\n";
return 1;
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "try again (1-120): ";
}
std::cout << "age " << age << '\n';
}조건은 읽기에 실패했거나 값이 범위 밖이면 참이 되어 본문으로 들어갑니다. 입력이 끝난 경우에는 다시 물어도 소용이 없으므로 오류를 알리고 끝내고, 그 밖에는 실패 상태를 지운 뒤 그 줄의 나머지를 모두 버리고 다시 묻습니다. 세 줄 abc, 150, 42를 차례로 입력하면 try again (1-120): 를 두 번 출력한 뒤 age 42를 출력합니다.
이 반복은 정수를 읽었는지와 값이 범위 안인지를 검사할 뿐, 한 줄 전체가 정수 하나인지는 검사하지 않습니다. 42abc를 입력하면 앞의 42를 읽는 데 성공하므로 그대로 받아들이고, 남은 abc는 입력에 그대로 남습니다. 줄 전체를 검사하려면 std::getline으로 한 줄을 읽은 뒤 따로 확인해야 하며, 이 방법은 std::string 클래스에서 봅니다.
더 깊이: 범위를 넘는 수를 읽으면
int에 담을 수 없을 만큼 큰 수를 입력해도 >>는 실패합니다. 이때 변수에는 0이 아니라 그 타입의 최댓값이나 최솟값이 저장되고 failbit가 설정됩니다. 예를 들어 32비트 int에 99999999999를 읽으면 변수는 2147483647이 되고 스트림은 실패 상태가 되며, 부호 없는 타입이라면 범위를 넘은 방향과 상관없이 최댓값이 저장됩니다. 그래서 read_age.cpp처럼 실패 여부를 먼저 보는 코드는 이런 입력도 거부하지만, 실패 여부를 확인하지 않고 값만 쓰는 코드는 최댓값을 정상 입력으로 착각할 수 있습니다.
int와 short는 표준 라이브러리 안에서 먼저 long으로 읽은 뒤 범위를 검사하도록 정해져 있습니다. 실패한 읽기에서 변수를 바꾸지 않던 예전 규칙은 결함 보고(defect report) LWG 696으로 고쳐졌으며, cppreference의 operator>> 설명에 이 규칙과 관련 결함 보고가 함께 정리되어 있습니다.
do-while 문
do-while 문은 본문을 먼저 실행하고, 그다음 조건을 검사해 참이면 다시 본문으로 돌아갑니다. 조건 뒤에 세미콜론이 붙는다는 점을 빠뜨리지 않아야 합니다.
do {
// 먼저 한 번 실행하고, 조건이 true인 동안 되풀이할 본문
} while (조건);메뉴처럼 일단 한 번은 보여 주고, 사용자의 선택에 따라 반복을 이어 갈지 정하는 일에 잘 맞습니다. 아래 프로그램의 본문은 메뉴를 출력하고 번호 하나를 읽어 switch로 처리하는 일이며, 조건 choice != 3이 이 본문을 3을 고를 때까지 되풀이하게 합니다. 정수가 아닌 입력을 받으면 choice를 믿을 수 없으므로, 조건까지 가지 않고 오류를 알린 뒤 바로 끝냅니다.
#include <iostream>
int main() {
int choice{};
do {
std::cout << "1 start, 2 settings, 3 quit\n";
if (!(std::cin >> choice)) {
std::cerr << "not an integer\n";
return 1;
}
switch (choice) {
case 1: std::cout << "starting\n"; break;
case 2: std::cout << "settings\n"; break;
case 3: std::cout << "bye\n"; break;
default: std::cout << "unknown\n"; break;
}
} while (choice != 3);
}입력이 2 5 3이면 출력은 다음과 같습니다. 세 수를 한 줄에 입력해도 >>는 반복마다 하나씩만 꺼내므로, 본문은 세 번 실행되고 메뉴도 세 번 출력됩니다.
1 start, 2 settings, 3 quit
settings
1 start, 2 settings, 3 quit
unknown
1 start, 2 settings, 3 quit
bye메뉴는 조건을 검사하기 전에 출력되므로 처음 한 번은 반드시 보입니다. 2와 5를 읽은 뒤에는 choice != 3이 참이어서 다시 메뉴를 보이고, 3을 읽으면 bye를 출력한 뒤 조건이 거짓이 되어 끝납니다. switch 안의 break는 switch만 끝내고 do-while은 끝내지 않는다는 점도 확인해 둡니다.
세 반복문 비교
반복문을 고를 때는 반복 횟수를 미리 아는지, 조건을 본문 전과 후 중 언제 검사해야 하는지를 먼저 가립니다.
| 특징 | for | while | do-while |
|---|---|---|---|
| 조건 검사 시점 | 본문 전 | 본문 전 | 본문 후 |
| 본문의 최소 실행 횟수 | 0번 | 0번 | 1번 |
| 반복 변수의 준비와 갱신 | 괄호 안에 함께 적음 | 루프 앞과 본문에서 직접 | 루프 앞과 본문에서 직접 |
| 잘 맞는 일 | 횟수가 정해진 반복, 인덱스 순회 | 입력이 끝날 때까지, 조건이 바뀔 때까지 | 먼저 한 번 보여 주거나 실행해야 하는 반복 |
검사 시점의 차이는 조건이 처음부터 거짓일 때 드러납니다. n이 3에서 시작하면 while (n < 3) { ++n; }은 본문을 한 번도 실행하지 않아 n이 3으로 남지만, do { ++n; } while (n < 3);은 본문을 한 번 실행해 n이 4가 됩니다.
끝나지 않는 반복 점검하기
조건이 계속 참으로 남으면 반복이 끝나지 않는 무한 루프(infinite loop)가 됩니다. 가장 흔한 원인은 조건에 쓰는 변수를 본문에서 바꾸지 않는 것입니다. while (counter < 5) { std::cout << counter; }는 본문이 counter를 바꾸지 않으므로 처음 조건이 참이면 영원히 참입니다.
본문에 갱신이 있어도, 조건으로 돌아가는 모든 경로가 그 갱신을 지나는지 확인해야 합니다. 아래 그림의 위쪽 코드는 skip()이 참인 회차에서 continue가 아래의 ++counter를 건너뛰고 바로 조건으로 돌아갑니다. skip()이 계속 참이면 counter가 0에 머물러 반복이 끝나지 않습니다.
종료 가능성은 모든 되돌아가는 경로에서 점검한다
skip=true를 명시한 한 반복에서 잘못된 continue 경로는 counter=0 유지, 수정 경로는 1로 증가한다. 독립된 두 프로그램의 진행 차이를 같은 프레임에서 비교한다.
PROGRESS · BACK EDGE · EXIT
모든 되돌아가는 경로에서 진행과 종료를 확인한다
조건식을 한 번 읽는 것만으로는 종료를 보장할 수 없습니다. 조건으로 되돌아가는 모든 경로에서 다음 검사 결과가 달라질 수 있는지 확인합니다.
잘못된 while은 continue에서 ++counter를 건너뛴다. 수정 버전에서는 작업을 건너뛰어도 ++counter를 수행한 뒤 조건으로 돌아간다. 두 프로그램은 독립 비교다.
관계도 해설과 예제 원자료
갱신을 건너뛸 수 있음
int counter = 0;
while (counter < 5) {
if (skip()) continue;
work();
++counter;
}
skip()이 계속 참이면 continue가 ++counter를 건너뛰어 조건은 계속 참입니다.
모든 정상 경로가 진행
int counter = 0;
while (counter < 5) {
if (!skip()) work();
++counter;
}
일반 경로와 작업을 건너뛰는 경로가 모두 갱신을 지난 뒤 다음 조건 검사로 돌아갑니다.
-
검사 시점을 표시한다
while과for는 본문 전,do-while은 본문 후에 검사합니다. 어떤 값이 다음 결과를 결정하는지 적습니다. -
되돌아가는 경로를 모두 추적한다
본문의 정상 끝뿐 아니라 조건문, 중첩 블록,
continue가 필요한 갱신을 우회하지 않는지 확인합니다. -
탈출 경로의 범위와 도달성을 확인한다
조건의
false는 반복을 끝냅니다. 이 장에서 다루는 문맥에서break는 자신을 둘러싼 반복문 또는switch문 가운데 가장 안쪽 문만 끝내므로, 중첩 구조에서는 어느 문을 대상으로 하는지 따로 봅니다.
while (true)나 for (;;)는 조건으로 종료하지 않겠다는 제어 형태입니다. 내부에서 끝나야 하는 작업이라면 도달 가능한 break, return 같은 별도 종료 계약을 명시하고, 계속 실행하도록 설계한 작업이라면 그 수명과 외부 중단 방식을 문서화합니다.이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 두 코드 사본 skip=true인 회차를 같은 counter=0에서 비교한다. |
|
|
| continue 경로 증가 위치가 제어 경로의 차이를 만든다. |
|
|
| 다음 검사 다음 반복 진입 상태를 나란히 읽는다. |
|
|
종료 가능성은 모든 되돌아가는 경로에서 점검한다
skip=true를 명시한 한 반복에서 잘못된 continue 경로는 counter=0 유지, 수정 경로는 1로 증가한다. 독립된 두 프로그램의 진행 차이를 같은 프레임에서 비교한다.
PROGRESS · BACK EDGE · EXIT
모든 되돌아가는 경로에서 진행과 종료를 확인한다
조건식을 한 번 읽는 것만으로는 종료를 보장할 수 없습니다. 조건으로 되돌아가는 모든 경로에서 다음 검사 결과가 달라질 수 있는지 확인합니다.
잘못된 while은 continue에서 ++counter를 건너뛴다. 수정 버전에서는 작업을 건너뛰어도 ++counter를 수행한 뒤 조건으로 돌아간다. 두 프로그램은 독립 비교다.
관계도 해설과 예제 원자료
갱신을 건너뛸 수 있음
int counter = 0;
while (counter < 5) {
if (skip()) continue;
work();
++counter;
}
skip()이 계속 참이면 continue가 ++counter를 건너뛰어 조건은 계속 참입니다.
모든 정상 경로가 진행
int counter = 0;
while (counter < 5) {
if (!skip()) work();
++counter;
}
일반 경로와 작업을 건너뛰는 경로가 모두 갱신을 지난 뒤 다음 조건 검사로 돌아갑니다.
-
검사 시점을 표시한다
while과for는 본문 전,do-while은 본문 후에 검사합니다. 어떤 값이 다음 결과를 결정하는지 적습니다. -
되돌아가는 경로를 모두 추적한다
본문의 정상 끝뿐 아니라 조건문, 중첩 블록,
continue가 필요한 갱신을 우회하지 않는지 확인합니다. -
탈출 경로의 범위와 도달성을 확인한다
조건의
false는 반복을 끝냅니다. 이 장에서 다루는 문맥에서break는 자신을 둘러싼 반복문 또는switch문 가운데 가장 안쪽 문만 끝내므로, 중첩 구조에서는 어느 문을 대상으로 하는지 따로 봅니다.
while (true)나 for (;;)는 조건으로 종료하지 않겠다는 제어 형태입니다. 내부에서 끝나야 하는 작업이라면 도달 가능한 break, return 같은 별도 종료 계약을 명시하고, 계속 실행하도록 설계한 작업이라면 그 수명과 외부 중단 방식을 문서화합니다.이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 두 코드 사본 skip=true인 회차를 같은 counter=0에서 비교한다. |
|
|
| continue 경로 증가 위치가 제어 경로의 차이를 만든다. |
|
|
| 다음 검사 다음 반복 진입 상태를 나란히 읽는다. |
|
|
그림의 아래쪽 코드처럼 if (!skip()) work(); 다음에 ++counter를 두면, 작업을 건너뛰는 회차에도 갱신을 지나 조건으로 돌아가므로 반복이 다섯 번 만에 끝납니다. 이처럼 본문의 정상적인 끝뿐 아니라 continue로 되돌아가는 경로까지 따라가며, 각 경로에서 다음 검사의 결과가 달라질 수 있는지 봅니다. continue는 다음 글에서 자세히 다룹니다.
일부러 끝나지 않게 만드는 반복도 있습니다. 서버나 게임의 메인 루프처럼 계속 돌아야 하는 경우에는 while (true)나 for (;;)로 의도를 드러내고, 본문 안에 실제로 도달할 수 있는 break나 return을 둡니다. 조건만 보고 끝나는 시점을 알 수 없으므로, 빠져나가는 조건을 본문의 앞쪽에 모아 두면 읽기 쉽습니다.
정리
while은 본문 전에 조건을 검사해 본문을 0번 실행할 수 있고,do-while은 본문 후에 검사해 적어도 한 번 실행합니다.while에는 초기화와 반복식 자리가 없으므로, 조건의 변수를 루프 앞에서 준비하고 본문에서 바꾸어야 합니다.while (std::cin >> x)는 읽기에 성공하는 동안 반복하며, 입력이 끝나거나 읽기에 실패하면 멈춥니다.- 읽기에 실패한 스트림은
clear()로 상태를 지우고ignore()로 남은 글자를 버린 뒤에야 다시 읽을 수 있습니다. do-while의 조건 뒤에는 세미콜론이 붙으며, 메뉴처럼 먼저 한 번 실행해야 하는 반복에 잘 맞습니다.- 무한 루프를 막으려면 조건으로 돌아가는 모든 경로가 조건을 바꾸는 갱신을 지나는지 확인합니다.
연습 문제
1. 검사 시점의 차이
n이 3에서 시작할 때 while (n < 3) { ++n; }과 do { ++n; } while (n < 3);의 본문 실행 횟수와 마지막 n은 각각 무엇인가요? n이 0에서 시작하면 어떻게 되나요?
해설 보기
3에서 시작하면 while은 본문을 0번 실행해 n이 3이고, do-while은 1번 실행해 n이 4입니다. 0에서 시작하면 둘 다 n이 0, 1, 2일 때 본문을 실행해 모두 3번 실행하고 n은 3이 됩니다. 한 입력에서 결과가 같다고 두 구조가 늘 같은 것은 아니므로, 조건이 처음부터 거짓인 경우를 따로 확인합니다.
2. 명령 읽기 따라가기
command_loop.cpp에 abc hello exit hello를 입력하면 무엇이 출력되나요?
해설 보기
abc에서 unknown, hello에서 hello를 출력하고, exit에서 break로 루프를 빠져나와 done을 출력합니다. 마지막 hello는 루프가 이미 끝났으므로 읽히지 않습니다. 출력은 unknown, hello, done의 세 줄입니다.
3. continue와 갱신
그림의 두 코드에서 skip()이 counter가 2일 때만 참을 돌려준다고 합시다. 위쪽 코드와 아래쪽 코드는 각각 어떻게 동작하나요? 아래쪽 코드에서 work()는 몇 번 실행되나요?
해설 보기
위쪽 코드는 counter가 2인 회차에서 continue가 ++counter를 건너뛰므로 counter가 2에 머물고, 다음 회차에도 skip()이 참이어서 반복이 끝나지 않습니다. 아래쪽 코드는 counter가 2인 회차에 work()만 건너뛰고 ++counter는 실행하므로, counter가 0, 1, 3, 4일 때 work()를 실행해 모두 4번 실행하고 5에서 끝납니다.
다음 글 break와 continue에서는 반복을 즉시 끝내는 break와 이번 회차의 나머지를 건너뛰는 continue가 각 반복문에서 어디로 흐름을 옮기는지 자세히 봅니다.