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
break와 continue는 다른 언어와 동일하다. 루프를 빠져나가거나 다음 반복으로 건너뛴다.
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편: 함수
Loading comments...