[Go] loop statement
Updated:
개요
Go 언어는 반복문으로 for 키워드만 제공하며, 다양한 형태로 사용할 수 있습니다.
기본 for 문
1. 전통적인 C 스타일 for 문
package main
import "fmt"
func main() {
// 기본 for 문
for i := 0; i < 10; i++ {
if i == 2 {
continue // 2는 건너뜀
}
if i == 8 {
break // 8에서 종료
}
fmt.Print(i, " ")
}
fmt.Println()
// 출력: 0 1 3 4 5 6 7
}
2. 조건문만 있는 for 문 (while 문 대체)
func whileStyle() {
count := 0
for count < 10 {
fmt.Print(count, " ")
count++
}
fmt.Println()
// 출력: 0 1 2 3 4 5 6 7 8 9
}
3. 무한 루프
func infiniteLoop() {
count := 0
for {
if count >= 5 {
break
}
fmt.Print(count, " ")
count++
}
fmt.Println()
// 출력: 0 1 2 3 4
}
range를 사용한 반복
1. 슬라이스/배열 순회
func rangeSlice() {
fruits := []string{"apple", "banana", "cherry"}
// 인덱스와 값 모두 사용
for index, value := range fruits {
fmt.Printf("[%d] %s\n", index, value)
}
// 값만 사용
for _, value := range fruits {
fmt.Println(value)
}
// 인덱스만 사용
for index := range fruits {
fmt.Println(index)
}
}
2. 맵(Map) 순회
func rangeMap() {
scores := map[string]int{
"Alice": 95,
"Bob": 87,
"Carol": 92,
}
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
// 키만 필요한 경우
for name := range scores {
fmt.Println(name)
}
}
3. 문자열 순회
func rangeString() {
str := "Hello, 世界"
// rune (유니코드 코드 포인트) 단위로 순회
for index, runeValue := range str {
fmt.Printf("%d: %c (%U)\n", index, runeValue, runeValue)
}
// 바이트 단위로 순회
for i := 0; i < len(str); i++ {
fmt.Printf("%d: %c\n", i, str[i])
}
}
4. 채널(Channel) 순회
func rangeChannel() {
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 반드시 채널을 닫아야 range가 종료됨
}()
for value := range ch {
fmt.Print(value, " ")
}
fmt.Println()
// 출력: 0 1 2 3 4
}
중첩 루프와 라벨
라벨을 사용한 중첩 루프 제어
func nestedLoopWithLabel() {
OuterLoop:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break OuterLoop // 외부 루프까지 종료
}
fmt.Printf("(%d,%d) ", i, j)
}
}
fmt.Println()
// 출력: (0,0) (0,1) (0,2) (1,0)
}
func continueWithLabel() {
OuterLoop:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue OuterLoop // 외부 루프의 다음 반복으로
}
fmt.Printf("(%d,%d) ", i, j)
}
}
fmt.Println()
// 출력: (0,0) (1,0) (2,0)
}
고급 활용 및 패턴
1. 역순 순회
func reverseIteration() {
nums := []int{1, 2, 3, 4, 5}
for i := len(nums) - 1; i >= 0; i-- {
fmt.Print(nums[i], " ")
}
fmt.Println()
// 출력: 5 4 3 2 1
}
2. 슬라이스 필터링
func filterSlice() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var evens []int
for _, num := range numbers {
if num%2 == 0 {
evens = append(evens, num)
}
}
fmt.Println(evens) // 출력: [2 4 6 8 10]
}
3. 동시 순회 (zip 패턴)
func zipIteration() {
names := []string{"Alice", "Bob", "Carol"}
ages := []int{25, 30, 28}
// 더 짧은 슬라이스 길이까지만 순회
minLen := len(names)
if len(ages) < minLen {
minLen = len(ages)
}
for i := 0; i < minLen; i++ {
fmt.Printf("%s is %d years old\n", names[i], ages[i])
}
}
성능 최적화 팁
1. 슬라이스 길이 캐싱
// 비효율적
for i := 0; i < len(slice); i++ {
// len(slice)가 매 반복마다 호출됨
}
// 효율적
length := len(slice)
for i := 0; i < length; i++ {
// 길이를 한 번만 계산
}
// range는 내부적으로 최적화되어 있음 (권장)
for i := range slice {
// 가장 효율적
}
2. range 복사 주의
type LargeStruct struct {
data [1000]int
}
func rangeCopy() {
items := []LargeStruct{{}, {}, {}}
// 비효율적: 각 반복마다 구조체 복사 발생
for _, item := range items {
_ = item
}
// 효율적: 인덱스로 접근
for i := range items {
_ = items[i]
}
// 또는 포인터 슬라이스 사용
itemPtrs := []*LargeStruct{{}, {}, {}}
for _, item := range itemPtrs {
_ = item // 포인터만 복사됨
}
}
일반적인 실수와 주의사항
1. range 변수 재사용 문제 (Go 1.21 이전)
// Go 1.21 이전: 주의 필요
func rangeVariableIssue() {
values := []int{1, 2, 3}
var funcs []func()
for _, v := range values {
// 잘못된 방법 (Go 1.21 이전)
funcs = append(funcs, func() {
fmt.Print(v, " ") // 모두 같은 v를 참조
})
}
for _, f := range funcs {
f() // 출력: 3 3 3 (예상: 1 2 3)
}
fmt.Println()
// 올바른 방법 (Go 1.21 이전)
funcs = nil
for _, v := range values {
v := v // 새 변수에 복사
funcs = append(funcs, func() {
fmt.Print(v, " ")
})
}
for _, f := range funcs {
f() // 출력: 1 2 3
}
fmt.Println()
}
// Go 1.22+: 자동으로 해결됨
// 각 반복마다 새로운 변수가 생성됨
2. 맵 순회 시 동시 수정 불가
func mapModificationDuringIteration() {
m := map[string]int{"a": 1, "b": 2, "c": 3}
// 위험: 순회 중 맵 수정
for key := range m {
if key == "b" {
delete(m, key) // 동작하지만 예측 불가능
}
}
// 안전한 방법: 삭제할 키 수집 후 처리
toDelete := []string{}
for key := range m {
if key == "b" {
toDelete = append(toDelete, key)
}
}
for _, key := range toDelete {
delete(m, key)
}
}
3. 슬라이스 순회 중 수정
func sliceModificationDuringIteration() {
numbers := []int{1, 2, 3, 4, 5}
// range는 초기 슬라이스의 복사본을 순회
for i, v := range numbers {
numbers[i] = v * 2 // OK
fmt.Print(numbers[i], " ")
}
fmt.Println()
// 출력: 2 4 6 8 10
// 주의: append로 확장해도 range는 원래 길이만 순회
numbers = []int{1, 2, 3}
for _, v := range numbers {
if v < 3 {
numbers = append(numbers, v+10)
}
fmt.Print(v, " ")
}
fmt.Println()
fmt.Println(numbers)
// 순회 출력: 1 2 3
// 최종 슬라이스: [1 2 3 11 12]
}
실전 예제
예제 1: 2차원 배열 순회
func traverse2DArray() {
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
for i, row := range matrix {
for j, val := range row {
fmt.Printf("matrix[%d][%d] = %d\n", i, j, val)
}
}
}
예제 2: 조건부 반복 종료
func findElement() {
numbers := []int{5, 2, 8, 1, 9, 3}
target := 8
found := false
for i, num := range numbers {
if num == target {
fmt.Printf("Found %d at index %d\n", target, i)
found = true
break
}
}
if !found {
fmt.Printf("%d not found\n", target)
}
}
예제 3: 슬라이스 변환
func transformSlice() {
words := []string{"hello", "world", "go"}
// 모든 문자열을 대문자로 변환
for i := range words {
words[i] = strings.ToUpper(words[i])
}
fmt.Println(words) // 출력: [HELLO WORLD GO]
}
벤치마크 비교
import "testing"
func BenchmarkForLoop(b *testing.B) {
data := make([]int, 1000)
for i := 0; i < b.N; i++ {
sum := 0
for j := 0; j < len(data); j++ {
sum += data[j]
}
}
}
func BenchmarkForRange(b *testing.B) {
data := make([]int, 1000)
for i := 0; i < b.N; i++ {
sum := 0
for _, v := range data {
sum += v
}
}
}
// 일반적으로 range가 더 빠르거나 비슷한 성능을 보임
정리
for는 Go의 유일한 반복문 키워드range는 슬라이스, 배열, 맵, 문자열, 채널 순회에 사용- 라벨을 사용하여 중첩 루프를 제어 가능
- Go 1.22부터 range 변수 스코핑이 개선됨
- 성능을 위해 불필요한 복사를 피하고 range 사용 권장
- 순회 중 컬렉션 수정 시 주의 필요