학습 자료

반복문 — 같은 일을 백 번 시킨다


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

세 칸이 각각 언제 실행되는지가 핵심이다.

for 가 도는 순서
  1. 01

    int i = 0 — 딱 한 번

    시작할 때 한 번만 실행된다. 반복될 때마다 다시 하지 않는다.

  2. 02

    i < 5 — 매번 먼저

    참이면 몸통으로 들어가고, 거짓이면 반복문을 빠져나간다.

  3. 03

    몸통 실행

    중괄호 안의 코드가 돈다.

  4. 04

    i++ — 몸통이 끝난 뒤

    그리고 다시 2번으로 돌아간다. 조건을 또 검사한다.

i0 부터 시작해서 4 까지 간다. i5 가 되는 순간 조건이 거짓이 되어 빠져나가므로, 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);
콘솔
  • 한 번은 돈다

m10 이라 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 은 왜 안 나왔을까. i7 일 때 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.

다시 짚어보기
  1. 01

    for 뒤에 세미콜론을 찍지 않는다

    몸통이 사라진다. 에러도 안 난다.

  2. 02

    i < 5 는 다섯 번, i <= 5 는 여섯 번

    off-by-one. 가장 흔한 실수다.

  3. 03

    while 은 조건을 갱신해야 끝난다

    빠뜨리면 무한 루프다. 컴파일러는 막지 않는다.

    while (n < 3) {
        System.out.print(n);
        n++;          // 이걸 빠뜨리면 안 끝난다
    }
  4. 04

    break 는 나가고 continue 는 건너뛴다

    중첩을 한 번에 빠져나오려면 이름표를 붙인다.

  5. 05

    읽기만 할 거면 for-each

    꺼낸 변수를 바꿔도 원본은 그대로다. 값이 복사되기 때문이다.

    for (int score : scores) { ... }

이번 강의에서 int[] 를 슬쩍 썼다. 2강에서 String[] args 를 넘겼고, 4강에서 참조형의 대표로 배열을 봤다. 계속 미뤄왔다.

다음 강의가 배열이다. 길이가 고정된 상자이고, 자바에서 유일하게 클래스가 아닌 참조형이다. 그래서 length 뒤에 괄호가 붙지 않는다.

반복문 — 같은 일을 백 번 시킨다 — 디코드랩(DCODELAB)