반복문 — 같은 일을 백 번 시킨다
Article
"안녕"을 다섯 번 찍으려고 한다.
for (int i = 0; i < 5; i++);
System.out.println("안녕");
- 안녕
한 번만 나왔다. 컴파일 에러도, 경고도 없다.
for 뒤에 붙은 세미콜론 하나 때문이다. 자바는 그 세미콜론을 "반복할 몸통이 없다"는 뜻으로 읽었다. 그래서 아무 일도 안 하는 반복을 다섯 번 하고, 그다음에 println 을 딱 한 번 실행했다.
들여쓰기는 사람만 읽는다. 7강에서 중괄호 얘기를 했던 이유가 이것이다.
for — 횟수가 정해졌을 때
for 의 괄호 안에는 세미콜론으로 나뉜 세 칸이 있다.
for (int i = 0; i < 5; i++) {
System.out.print(i + " ");
}
- 0 1 2 3 4
세 칸이 각각 언제 실행되는지가 핵심이다.
- 01
int i = 0— 딱 한 번시작할 때 한 번만 실행된다. 반복될 때마다 다시 하지 않는다.
- 02
i < 5— 매번 먼저참이면 몸통으로 들어가고, 거짓이면 반복문을 빠져나간다.
- 03
몸통 실행
중괄호 안의 코드가 돈다.
- 04
i++— 몸통이 끝난 뒤그리고 다시 2번으로 돌아간다. 조건을 또 검사한다.
i 는 0 부터 시작해서 4 까지 간다. i 가 5 가 되는 순간 조건이 거짓이 되어 빠져나가므로, 5 는 찍히지 않는다.
다섯 번 돌리고 싶으면 i < 5 라고 쓴다. i <= 5 라고 쓰면 여섯 번 돈다. 프로그래밍에서 가장 흔한 실수라 이름까지 붙어 있다. 하나 차이로 어긋난다고 해서 off-by-one 이라고 부른다.
i 는 반복문 안에서만 산다
for 의 첫 칸에서 선언한 변수는 반복문 밖에서 쓸 수 없다.
for (int i = 0; i < 3; i++) { }
System.out.println(i);
- error: cannot find symbol
- symbol: variable i
3강에서 본 cannot find symbol 과 같은 에러 메시지다. 다만 원인이 다르다. 그때는 타입을 안 적어서 이름이 만들어지지 않았고, 지금은 만들어졌던 i 가 중괄호가 닫히는 순간 사라졌다.
변수가 어디까지 살아 있는지를 스코프라고 부른다. 중괄호가 스코프의 경계다. 밖에서도 써야 한다면 밖에서 선언한다.
while — 몇 번인지 모를 때
조건이 참인 동안 계속 돈다.
int n = 0;
while (n < 3) {
System.out.print(n + " ");
n++;
}
- 0 1 2
for 와 하는 일이 같아 보인다. 실제로 서로 바꿔 쓸 수 있다. 다만 횟수가 정해져 있으면 for, 조건이 언제 거짓이 될지 모르면 while 을 쓴다.
while 에는 큰 함정이 있다. n++ 를 빠뜨려 보자.
int n = 0;
while (n < 3) {
System.out.print(n + " ");
}
n 은 영원히 0 이다. 조건은 영원히 참이다. 프로그램이 멈추지 않는다.
컴파일러는 이걸 막지 않는다. 문법이 틀린 게 없기 때문이다. for 는 증감식이 괄호 안에 들어 있어서 빠뜨리기 어렵지만, while 은 몸통 어딘가에 있어야 한다.
do-while — 일단 한 번은 한다
조건을 몸통 뒤에서 검사한다. 그래서 조건이 처음부터 거짓이어도 한 번은 실행된다.
int m = 10;
do {
System.out.println("한 번은 돈다");
} while (m < 5);
- 한 번은 돈다
m 은 10 이라 m < 5 는 처음부터 거짓이다. 그래도 한 번 찍혔다.
while 뒤에 세미콜론이 붙는다. 이 문법에서만 그렇다.
쓸 일이 많지는 않다. "일단 입력을 한 번 받고, 잘못됐으면 다시 받는다" 같은 상황에 맞는다.
for
횟수가 정해졌다
- 0부터 9까지
- 배열의 처음부터 끝까지
- 시작·조건·증감이 한 줄에 모여 있다
- 가장 많이 쓴다
while / do-while
조건이 언제 끝날지 모른다
- 사용자가 그만두겠다고 할 때까지
- 파일 끝에 닿을 때까지
- 조건 갱신을 잊으면 무한 루프
do-while은 최소 한 번 실행된다
break 와 continue
break 는 반복문을 통째로 빠져나온다. continue 는 이번 회차만 건너뛰고 다음 회차로 간다.
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue; // 짝수는 건너뛴다
if (i > 6) break; // 6을 넘으면 그만둔다
System.out.print(i + " ");
}
- 1 3 5
7 은 왜 안 나왔을까. i 가 7 일 때 continue 는 통과했지만 i > 6 이 참이라 break 로 빠져나갔기 때문이다. 순서대로 읽으면 답이 나온다.
반복문 두 개를 한 번에 빠져나오기
break 는 자기가 속한 반복문 하나만 빠져나온다. 중첩된 반복문에서는 안쪽만 끝나고 바깥은 계속 돈다.
바깥까지 한 번에 나오려면 이름표를 붙인다.
outer:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j > 4) break outer;
System.out.print(i + "*" + j + "=" + (i * j) + " ");
}
}
- 1*1=1 1*2=2 1*3=3 2*1=2 2*2=4
i=2, j=3 에서 곱이 6 이 되자 두 반복문을 한꺼번에 빠져나왔다.
이름표는 아껴 쓴다. 두세 겹 이상 중첩되고 있다면, 대개는 10강에서 배울 메서드로 쪼개는 게 낫다.
for-each — 이름은 없고 값만 필요할 때
담긴 값을 처음부터 끝까지 하나씩 꺼내 쓸 때는 더 짧게 쓸 수 있다.
int[] scores = { 10, 20, 30 };
for (int score : scores) {
System.out.print(score + " ");
}
- 10 20 30
scores 에서 값을 하나씩 꺼내 score 에 넣어준다는 뜻이다. : 은 "안에서 꺼낸다"로 읽는다.
i 도 없고 조건도 없고 증감도 없다. off-by-one 이 아예 불가능하다. 번호가 필요 없다면 이쪽이 안전하다.
여기 쓰인 int[] 가 배열이다. 다음 강의의 주제이니 지금은 "값 여러 개를 담는 것" 정도로 두자.
다만 한 가지는 지금 짚어둔다.
int[] a = { 1, 2, 3 };
for (int v : a) {
v = 99;
}
System.out.println(a[0]);
- 1
99 로 바뀌지 않았다.
정리하면
횟수를 알면 for, 모르면 while, 값만 필요하면 for-each.
- 01
for뒤에 세미콜론을 찍지 않는다몸통이 사라진다. 에러도 안 난다.
- 02
i < 5는 다섯 번,i <= 5는 여섯 번off-by-one. 가장 흔한 실수다.
- 03
while은 조건을 갱신해야 끝난다빠뜨리면 무한 루프다. 컴파일러는 막지 않는다.
while (n < 3) { System.out.print(n); n++; // 이걸 빠뜨리면 안 끝난다 } - 04
break는 나가고continue는 건너뛴다중첩을 한 번에 빠져나오려면 이름표를 붙인다.
- 05
읽기만 할 거면
for-each꺼낸 변수를 바꿔도 원본은 그대로다. 값이 복사되기 때문이다.
for (int score : scores) { ... }
이번 강의에서 int[] 를 슬쩍 썼다. 2강에서 String[] args 를 넘겼고, 4강에서 참조형의 대표로 배열을 봤다. 계속 미뤄왔다.
다음 강의가 배열이다. 길이가 고정된 상자이고, 자바에서 유일하게 클래스가 아닌 참조형이다. 그래서 length 뒤에 괄호가 붙지 않는다.