(C#) 7. 코드의 흐름 제어 (while)
조건이 참인 동안 반복하는 while과, 최소 한 번은 실행하는 do-while의 차이. 입력 루프에서 ReadLine이 null을 돌려줄 때 무한 루프가 되는 문제까지 확인했다.
같은 코드를 여러 번
6편의 가위바위보는 한 판 하고 프로그램이 끝난다. 다시 하려면 실행을 다시 해야 한다. 반복이 필요하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
namespace WhileEX
{
class Program
{
static void Main(string[] args)
{
// while 반복문
int count = 5;
string answer;
while (count > 0)
{
Console.WriteLine("Hello World!");
count--;
}
do
{
Console.WriteLine("(y/n) : ");
answer = Console.ReadLine();
} while (answer != "y");
Console.WriteLine("end");
}
}
}
조건을 언제 검사하는가
while과 do-while의 차이는 조건을 먼저 보느냐 나중에 보느냐다.
1
2
3
4
5
int count = 0;
while (count > 0) { } // 한 번도 실행되지 않는다
do { } while (count > 0); // 한 번은 실행된다
이 차이가 어디서 의미가 있는지는 아래 입력 루프에서 바로 보인다. 사용자에게 물어보려면 일단 한 번은 물어봐야 하고, 그 답을 봐야 다시 물을지 정할 수 있다. while로 쓰면 조건에 넣을 answer가 아직 없어서 어색해진다.
1
2
3
4
5
6
7
// while 로 쓰면 이렇게 된다
string answer = "";
while (answer != "y")
{
Console.WriteLine("(y/n) : ");
answer = Console.ReadLine();
}
answer를 억지로 ""로 초기화해야 한다. 조건을 만족시키기 위한 가짜 초기값이라 의미가 없다. do-while이 이 상황에 맞는 문법이다.
조건을 바꾸는 코드를 빠뜨리면
1
2
3
4
5
while (count > 0)
{
Console.WriteLine("Hello World!");
count--; // 이게 없으면 영원히 돈다
}
count--를 빼면 count가 계속 5라서 조건이 절대 거짓이 안 된다. 화면이 순식간에 채워지고 CPU 하나를 다 쓴다.
while을 쓸 때는 조건에 나오는 변수가 루프 안에서 바뀌는지를 확인하는 습관이 생겼다. 이걸 한 줄로 묶어주는 게 다음 편의 for다.
ReadLine 이 null 이면 빠져나오지 못한다
do-while 쪽에서 실제로 걸린 문제다.
Console.ReadLine()은 입력이 끝나면 null을 돌려준다. 콘솔에서 Ctrl+Z(리눅스는 Ctrl+D)를 누르거나, 파일을 파이프로 넣었는데 그 안에 y가 없으면 그렇게 된다.
null != "y"는 참이다. 그래서 조건이 계속 만족되고, 다시 ReadLine을 부르면 또 null이 온다. 화면에 (y/n) :만 무한히 찍히면서 멈추지 않는다.
1
2
3
4
5
6
7
string answer;
do
{
Console.Write("(y/n) : ");
answer = Console.ReadLine();
if (answer == null) return; // 입력이 끝났다
} while (answer != "y");
while 조건에서 끝내기 어려운 경우라 루프 안에서 빠져나가게 했다. 조건이 두 개 이상이 되면 while (true) + break 쪽이 오히려 읽기 편할 때가 많다.
1
2
3
4
5
6
7
8
9
10
11
while (true)
{
Console.Write("(y/n) : ");
string answer = Console.ReadLine();
if (answer == null) return;
if (answer == "y") break;
if (answer == "n") { Console.WriteLine("종료합니다."); return; }
Console.WriteLine("y 또는 n 을 입력하세요.");
}
조건을 한 줄에 욱여넣지 않고 각각의 경우를 나열하니 무엇을 처리하고 무엇을 안 하는지가 보인다.
대문자 Y 를 입력하면 안 끝난다
answer != "y"는 정확히 소문자 y만 통과시킨다. Y를 치면 계속 물어본다. 사용자 입장에서는 왜 안 되는지 알 수 없다.
1
if (string.Equals(answer, "y", StringComparison.OrdinalIgnoreCase)) break;
answer.ToLower() == "y"로도 되지만, 문화권에 따라 대소문자 규칙이 다른 경우가 있어서 비교 목적이면 OrdinalIgnoreCase가 안전하다는 걸 나중에 알았다. 앞뒤 공백도 흔하니 Trim()을 같이 걸어두면 덜 성가시다.
while(true) 를 게임 루프로 쓸 때
게임은 결국 “입력 받고, 갱신하고, 그리고”를 계속 반복하는 구조다.
1
2
3
4
5
6
while (true)
{
ProcessInput();
Update();
Render();
}
이렇게만 두면 CPU 코어 하나를 100% 쓴다. 콘솔 프로그램에서는 Console.ReadLine()이 입력을 기다리며 막아주니 문제가 안 되는데, 입력을 기다리지 않는 루프면 직접 쉬어줘야 한다.
1
System.Threading.Thread.Sleep(16); // 대략 60 FPS
정확한 프레임 제어는 이보다 복잡하지만, 처음에는 이 정도만 알아도 노트북 팬이 도는 걸 막을 수 있다.
정리하면
while은 조건을 먼저 보고,do-while은 한 번 실행한 뒤에 본다- 사용자에게 물어보고 답에 따라 반복할지 정하는 구조는
do-while이 자연스럽다 - 조건에 쓰인 변수가 루프 안에서 바뀌는지 확인한다. 안 바뀌면 무한 루프다
Console.ReadLine()은null을 돌려줄 수 있고,null != "y"는 참이라 루프를 빠져나오지 못한다- 종료 조건이 여러 개면
while (true)+break가 읽기 쉽다 - 문자열 비교는 대소문자와 공백을 고려한다
- 입력을 기다리지 않는 무한 루프는 CPU를 다 쓴다