Go에서 반복 숙달하기: for 루프 심층 분석
Lukas Schneider
DevOps Engineer · Leapcell

Go의 반복에 대한 접근 방식은 우아하면서도 실용적입니다. while
, do-while
및 기타 다양한 루프 구문을 제공하는 다른 많은 언어와 달리 Go는 모든 것을 단일하고 매우 유연한 for
키워드로 단순화합니다. 이처럼 제한적인 선택처럼 보이지만 실제로는 엄청난 힘과 명확성을 제공하여 개발자가 다양한 반복 패턴을 쉽게 표현할 수 있도록 합니다. 이 글에서는 Go의 for
루프의 다양한 형태, 즉 전통적인 C 스타일의 세 가지 구성 요소 루프와 관용적인 for-range
루프를 심층적으로 살펴보겠습니다.
전통적인 for
루프: 세Component 형태
Go의 for
루프의 가장 기본적이고 널리 인식되는 형태는 C 스타일 for
루프와 동등하며, 세미콜론으로 구분되는 세 가지 선택적 구성 요소(initialization
, condition
, post-statement
)로 구성됩니다.
일반적인 구문은 다음과 같습니다.
for initialization; condition; post-statement { // 루프 본문 }
각 구성 요소를 살펴보겠습니다.
-
initialization
: 이 문은 루프가 시작되기 전에 한 번 실행됩니다. 일반적으로 루프 카운터 또는 루프 실행에 필요한 변수를 선언하고 초기화하는 데 사용됩니다. 여기서 선언된 변수는for
루프에 만 범위가 지정됩니다. -
condition
: 이 부울 표현식은 각 반복 전에 평가됩니다.true
로 평가되면 루프 본문이 실행됩니다.false
로 평가되면 루프가 종료됩니다. 생략하면 조건은 기본적으로true
가 되어 무한 루프(break
를 사용하여 종료할 수 있음)를 만듭니다. -
post-statement
: 이 문은 루프 본문의 각 반복 후에 실행됩니다. 루프 카운터를 업데이트하는 데 자주 사용됩니다(예:i++
).
0부터 4까지 반복하는 고전적인 예는 다음과 같습니다.
package main import "fmt" func main() { fmt.Println("---", "\n", "전통적인 for 루프 ---") for i := 0; i < 5; i++ { fmt.Printf("반복 %d\n", i) } }
출력:
--- 전통적인 for 루프 ---
반복 0
반복 1
반복 2
반복 3
반복 4
유연성: 구성 요소 생략
Go의 for
루프 디자인은 세 가지 구성 요소가 모두 선택 사항이기 때문에 놀랍도록 유연합니다.
1. initialization
및 post-statement
생략 (while
루프)
initialization
및 post-statement
부분을 생략하면 for
루프는 다른 언어의 while
루프처럼 동작합니다. condition
이 있는 경우에도 세미콜론이 필요합니다.
package main import "fmt" func main() { sum := 1 fmt.Println("\n---", "\n", "'while' 루프로서의 For 루프 ---") for sum < 1000 { // init 또는 post-statement 생략 sum += sum fmt.Printf("현재 합계: %d\n", sum) } fmt.Printf("'while' 루프 후 최종 합계: %d\n", sum) }
출력:
--- 'while' 루프로서의 For 루프 ---
현재 합계: 2
현재 합계: 4
현재 합계: 8
현재 합계: 16
현재 합계: 32
현재 합계: 64
현재 합계: 128
현재 합계: 256
현재 합계: 512
현재 합계: 1024
'while' 루프 후 최종 합계: 1024
initialization
이 생략되면 sum
은 루프 외부에서 선언되어야 루프 내부 및 종료 후 접근할 수 있습니다.
2. 모든 구성 요소 생략 (무한 루프)
세 가지 구성 요소를 모두 생략하면 무한 루프가 만들어집니다. 이 패턴은 서버, 백그라운드 프로세스 또는 break
문에 의존하여 루프를 종료하는 상황에 유용합니다.
package main import ( "fmt" "time" ) func main() { fmt.Println("\n---", "\n", "무한 for 루프 ---") counter := 0 for { // 계속 반복 fmt.Printf("틱 %d...\n", counter) counter++ time.Sleep(500 * time.Millisecond) // 작업 시뮬레이션 if counter >= 3 { fmt.Println("무한 루프 종료.") break // 루프 종료 } } fmt.Println("무한 루프가 종료되었습니다.") }
출력:
--- 무한 for 루프 ---
틱 0...
틱 1...
틱 2...
무한 루프 종료.
무한 루프가 종료되었습니다.
for-range
루프: 컬렉션 반복
세 가지 구성 요소 for
루프는 카운터 기반 반복에 훌륭하지만, Go는 배열, 슬라이스, 문자열, 맵 및 채널과 같은 컬렉션을 반복하는 더 편리하고 관용적인 방법을 제공합니다. 바로 for-range
루프입니다.
for-range
루프는 컬렉션의 요소에 대한 인덱스/키 및 값에 대한 액세스를 제공하여 수동 인덱스 관리가 필요 없도록 함으로써 반복을 단순화합니다.
일반적인 구문은 다음과 같습니다.
for index, value := range collection { // 루프 본문 }
또는 값만 필요한 경우:
for _, value := range collection { // 루프 본문 }
인덱스/키만 필요한 경우:
for index := range collection { // value는 암시적으로 무시됨 // 루프 본문 }
다양한 데이터 유형으로 사용법을 살펴보겠습니다.
1. 슬라이스 및 배열 반복
슬라이스 또는 배열을 반복할 때 range
는 각 요소에 대해 인덱스와 요소 값의 복사본이라는 두 개의 값을 반환합니다.
package main import "fmt" func main() { numbers := []int{10, 20, 30, 40, 50} fmt.Println("\n---", "\n", "슬라이스에 대한 For-range ---") for i, num := range numbers { fmt.Printf("인덱스: %d, 값: %d\n", i, num) } // 값에만 관심이 있는 경우 fmt.Println("\n---", "\n", "슬라이스에 대한 For-range (값만) ---") total := 0 for _, num := range numbers { // 인덱스를 무시하려면 빈 식별자 '_' 사용 total += num } fmt.Printf("숫자의 총합: %d\n", total) // 인덱스에만 관심이 있는 경우 fmt.Println("\n---", "\n", "슬라이스에 대한 For-range (인덱스만) ---") for i := range numbers { // 값은 반환되거나 사용되지 않음 fmt.Printf("인덱스 %d의 요소 처리 중\n", i) } }
출력:
--- 슬라이스에 대한 For-range ---
인덱스: 0, 값: 10
인덱스: 1, 값: 20
인덱스: 2, 값: 30
인덱스: 3, 값: 40
인덱스: 4, 값: 50
--- 슬라이스에 대한 For-range (값만) ---
숫자의 총합: 150
--- 슬라이스에 대한 For-range (인덱스만) ---
인덱스 0의 요소 처리 중
인덱스 1의 요소 처리 중
인덱스 2의 요소 처리 중
인덱스 3의 요소 처리 중
인덱스 4의 요소 처리 중
2. 문자열 반복
문자열을 반복할 때 range
는 시작 문자의 바이트 인덱스와 해당 유니코드 룬(문자)을 제공합니다. 이는 멀티바이트 유니코드 문자를 올바르게 처리하는 데 중요합니다.
package main import "fmt" func main() { sentence := "Hello, 世界" // "世界"는 멀티바이트 유니코드 문자입니다. fmt.Println("\n---", "\n", "문자열에 대한 For-range ---") for i, r := range sentence { fmt.Printf("바이트 인덱스: %2d, 룬: '%c' (유니코드: %U)\n", i, r, r) } }
출력:
--- 문자열에 대한 For-range ---
바이트 인덱스: 0, 룬: 'H' (유니코드: U+0048)
바이트 인덱스: 1, 룬: 'e' (유니코드: U+0065)
바이트 인덱스: 2, 룬: 'l' (유니코드: U+006C)
바이트 인덱스: 3, 룬: 'l' (유니코드: U+006C)
바이트 인덱스: 4, 룬: 'o' (유니코드: U+006F)
바이트 인덱스: 5, 룬: ',' (유니코드: U+002C)
바이트 인덱스: 6, 룬: ' ' (유니코드: U+0020)
바이트 인덱스: 7, 룬: '世' (유니코드: U+4E16)
바이트 인덱스: 10, 룬: '界' (유니코드: U+754C)
바이트 인덱스가 6에서 7로, 그 다음 멀티바이트 문자 世
와 界
에 대해 7에서 10으로 건너뛰는 것을 확인하세요. range
는 개별 바이트가 아닌 룬을 올바르게 반복합니다.
3. 맵 반복
맵을 반복할 때 range
는 키-값 쌍을 반환합니다. 맵 요소에 대한 반복 순서는 보장되지 않으며 다양할 수 있습니다.
package main import "fmt" func main() { scores := map[string]int{ "Alice": 95, "Bob": 88, "Charlie": 92, } fmt.Println("\n---", "\n", "맵에 대한 For-range ---") for name, score := range scores { fmt.Printf("%s가 %d점을 받았습니다.\n", name, score) } // 키에만 관심이 있는 경우 fmt.Println("\n---", "\n", "맵에 대한 For-range (키만) ---") fmt.Println("반의 학생들:") for name := range scores { // Score는 반환되거나 사용되지 않습니다. fmt.Printf("- %s\n", name) } }
출력 (순서는 다를 수 있음):
--- 맵에 대한 For-range ---
Alice가 95점을 받았습니다.
Bob이 88점을 받았습니다.
Charlie가 92점을 받았습니다.
--- 맵에 대한 For-range (키만) ---
반의 학생들:
- Alice
- Bob
- Charlie
4. 채널 반복
채널을 반복할 때 range
는 채널이 닫힐 때까지 채널에서 값을 읽습니다.
package main import ( "fmt" "time" ) func main() { fmt.Println("\n---", "\n", "채널에 대한 For-range ---") ch := make(chan int) // 채널에 숫자를 보내는 고루틴 go func() { for i := 0; i < 3; i++ { ch <- i * 10 time.Sleep(100 * time.Millisecond) } close(ch) // 보내기 완료 시 채널을 닫는 것이 중요합니다. }() // 채널에서 숫자를 받는 메인 고루틴 for num := range ch { fmt.Printf("수신: %d\n", num) } fmt.Println("채널이 닫혔고 반복이 완료되었습니다.") }
출력:
--- 채널에 대한 For-range ---
수신: 0
수신: 10
수신: 20
채널이 닫혔고 반복이 완료되었습니다.
for-range
루프는 보낸 사람이 ch
채널을 명시적으로 close
할 때까지 값을 계속 수신합니다. 채널이 닫히지 않으면 루프가 무기한 차단되어 더 이상 값이 전송되지 않으면 교착 상태가 발생합니다.
언제 어떤 것을 사용해야 할까?
-
세 가지 구성 요소
for
루프:- 루프 카운터를 정확하게 제어해야 할 때(예: 특정 횟수 반복, 한 번에 하나 이상 단계, 역방향 반복).
- 반복 논리가 컬렉션에 직접 매핑되지 않을 때.
- "while" 또는 "무한" 루프 패턴을 구현할 때.
-
for-range
루프:- 슬라이스, 배열, 문자열, 맵 및 채널의 요소를 반복하는 선호되는 방법.
- 인덱스/키와 값 모두(또는 둘 중 하나)가 필요할 때.
- 컬렉션 순회를 위한 명확하고 간결하며 관용적인 코드를 원할 때.
- 컬렉션 길이를 자동으로 처리하고 문자열의 유니코드 복잡성을 처리하여 일반적인 오프-바이-원 오류를 줄입니다.
결론
Go의 통합 for
루프는 단순성과 강력함이라는 설계 철학의 증거입니다. 모든 반복 구문을 단일 키워드로 통합함으로써 Go는 학습 곡선을 간소화하고 일관성을 조성합니다. 전통적인 세 가지 구성 요소 for
루프는 순차적 및 조건부 반복에 대한 세밀한 제어를 제공하며, for-range
루프는 컬렉션을 순회하는 우아하고 안전한 방법을 제공합니다. 두 가지 형태 모두를 숙달하는 것은 효과적이고 관용적인 Go 프로그램을 작성하는 데 기본입니다. 이러한 강력한 반복 도구를 채택하면 데이터 효율적으로 처리하고 강력한 애플리케이션을 구축할 수 있습니다.