Skip to content
ioob.dev
Go back

Go 입문 2편 — 조건문과 반복문

· 3분 읽기

Table of contents

if — 초기화문이 붙는다

Go의 if는 다른 언어와 크게 다르지 않다. 다만 한 가지 독특한 점이 있는데, 조건 앞에 초기화문을 넣을 수 있다.

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 일반적인 if
    x := 10
    if x > 5 {
        fmt.Println("x는 5보다 크다")
    }

    // 초기화문이 있는 if
    if n, err := strconv.Atoi("42"); err == nil {
        fmt.Println("변환 성공:", n)
    } else {
        fmt.Println("변환 실패:", err)
    }
    // 여기서는 n, err에 접근할 수 없다
}

if n, err := strconv.Atoi("42"); err == nil — 세미콜론 앞이 초기화문, 뒤가 조건이다. 이렇게 선언된 변수는 if-else 블록 안에서만 쓸 수 있고, 밖에서는 사라진다.

이 패턴이 왜 유용할까? Go에서는 에러를 반환하는 함수가 많다. 그 결과를 확인하고 바로 처리하는 코드를 한 덩어리로 묶을 수 있으니 스코프가 깔끔해진다. 실무 코드에서 정말 자주 보이는 패턴이니 익숙해지는 게 좋다.

참고로 Go에서는 if 조건에 괄호를 쓰지 않는다. 써도 컴파일은 되지만, gofmt가 자동으로 벗겨버린다.

switch — fallthrough가 없다

대부분의 C 계열 언어에서 switch를 쓸 때 break를 빠뜨려서 버그가 생긴 경험이 있을 것이다. Go는 이 문제를 원천적으로 해결했다. 각 case가 자동으로 break된다.

package main

import "fmt"

func main() {
    day := ""

    switch day {
    case "":
        fmt.Println("한 주의 시작")
    case "", "", "":
        fmt.Println("주중")
    case "":
        fmt.Println("불금!")
    default:
        fmt.Println("주말")
    }
}

하나의 case에 여러 값을 콤마로 나열할 수 있다. break를 쓸 필요가 없으니 코드가 간결하고 실수할 여지도 줄어든다.

혹시 C처럼 다음 case로 흘러 내려가게 하고 싶다면 fallthrough를 명시하면 된다. 하지만 실무에서 fallthrough를 쓰는 경우는 극히 드물다.

Go의 switch에는 더 강력한 기능이 있다. 조건 없는 switch다.

package main

import "fmt"

func main() {
    score := 85

    switch {
    case score >= 90:
        fmt.Println("A")
    case score >= 80:
        fmt.Println("B")
    case score >= 70:
        fmt.Println("C")
    default:
        fmt.Println("F")
    }
}

switch 뒤에 값을 적지 않으면, 각 case가 독립적인 조건으로 평가된다. if-else if-else if를 길게 늘어놓는 것보다 읽기 좋을 때가 많다. 특히 조건이 서너 개 이상 이어질 때 이 패턴이 빛을 발한다.

for — Go의 유일한 반복문

Go에는 while도 없고 do-while도 없다. 반복문은 for 하나뿐이다. 대신 for가 여러 형태로 변신한다.

package main

import "fmt"

func main() {
    // 1. 전통적인 for
    for i := 0; i < 5; i++ {
        fmt.Print(i, " ")
    }
    fmt.Println()
    // 0 1 2 3 4

    // 2. while처럼 쓰기
    count := 0
    for count < 3 {
        fmt.Print(count, " ")
        count++
    }
    fmt.Println()
    // 0 1 2

    // 3. 무한 루프
    n := 0
    for {
        if n >= 3 {
            break
        }
        fmt.Print(n, " ")
        n++
    }
    fmt.Println()
    // 0 1 2
}

세 번째 형태인 for {}는 무한 루프다. 서버의 메인 루프나 이벤트 처리에서 흔히 쓰인다. 다른 언어의 while(true)와 같은 역할인데, Go에서는 이게 관용적 표현이다.

컬렉션을 순회할 때는 range를 쓴다.

package main

import "fmt"

func main() {
    fruits := []string{"사과", "바나나", "체리"}

    for i, fruit := range fruits {
        fmt.Printf("%d: %s\n", i, fruit)
    }

    // 인덱스가 필요 없으면 _로 버린다
    for _, fruit := range fruits {
        fmt.Println(fruit)
    }
}

range는 인덱스와 값을 동시에 돌려준다. 인덱스가 필요 없을 때 _(블랭크 식별자)로 무시하는 건 Go에서 아주 자주 쓰이는 패턴이다. 변수를 선언해놓고 안 쓰면 컴파일 에러가 나기 때문에, _가 없으면 불필요한 변수를 억지로 써야 한다.

break, continue, 그리고 label

breakcontinue는 다른 언어와 동일하다. 루프를 빠져나가거나 다음 반복으로 건너뛴다.

package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        if i == 3 {
            continue  // 3은 건너뛰기
        }
        if i == 7 {
            break     // 7에서 멈추기
        }
        fmt.Print(i, " ")
    }
    fmt.Println()
    // 0 1 2 4 5 6
}

중첩 루프에서 바깥 루프를 제어하고 싶을 때는 label을 쓴다.

package main

import "fmt"

func main() {
outer:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if i == 1 && j == 1 {
                break outer  // 바깥 루프까지 한 번에 탈출
            }
            fmt.Printf("(%d, %d) ", i, j)
        }
    }
    fmt.Println()
    // (0, 0) (0, 1) (0, 2) (1, 0)
}

outer:가 레이블이고, break outer가 해당 레이블이 붙은 루프를 탈출한다. Java에도 비슷한 기능이 있지만, 잘 모르는 사람이 많다. Go에서는 중첩 루프 탈출이 필요한 상황에서 이 패턴을 자연스럽게 사용한다.

continue에도 레이블을 쓸 수 있다.

package main

import "fmt"

func main() {
outer:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 1 {
                continue outer  // 바깥 루프의 다음 반복으로
            }
            fmt.Printf("(%d, %d) ", i, j)
        }
    }
    fmt.Println()
    // (0, 0) (1, 0) (2, 0)
}

레이블을 남발하면 코드가 복잡해지니 꼭 필요한 곳에서만 쓰는 게 좋다. 대부분의 경우 중첩 루프 자체를 함수로 분리하는 편이 더 깔끔하다.


다음 편에서는 Go의 함수를 다룬다. 다중 반환값, 클로저, 일급 함수 등 Go 함수만의 특징을 살펴보자.

-> 3편: 함수


Share this post on:

Comments

Loading comments...


Previous Post
Go 입문 3편 — 함수
Next Post
Go 입문 1편 — 변수, 상수, 타입