Go에서 range의 모든 것

Go에서 range의 모든 것

Go 언어의 내장 함수들은 많지 않지만, 각각이 일당백 역할을 한다. make, new, range는 Go 프로그램을 작성할 때 넓은 범위에서 사용되는 내장 함수이다. 이번 포스팅에서는 range 활용에 대해서 정리해두었다.

rangefor와 함께 사용된다. range의 뒷 부분으로 배열, 슬라이스, 맵, 또는 채널(특히, 값을 받는 역할을 하는)이 들어올 수 있다. 이터레이션 값들 (iteration values라고 표현함)이 있는 경우 그 값을 할당해주고 반복문의 블록으로 진입하게 된다.

1
2
3
4
for i, val := range intSlice {
...
}
// 가장 많이 사용되는 슬라이스 순환

range 문의 오른쪽 영역을 range expression이라고 하는데, 이 영역에는 다음 표현들이 들어올 수 있다.

  • 배열, 배열 포인터
  • 슬라이스
  • 문자열
  • 채널 (받기 동작을 할 수 있어야 한다.)

range 왼쪽의 = 또는 := 기준으로 그 다음 왼쪽 영역은 이터레이션 변수(iteration variables)라고 하는데, range expression이 채널인 경우는 최대 1개의 이터레이션 변수가 가능하고, 나머지는 최대 2개까지 가능하다.

0개 부터 2개까지 사용이 가능하다, 1개만 사용하면, 첫 번째 이터레이션 값이 할당된다.

1
2
3
for [`iteration variables`] := range `range expression`
// `range`는 `range expression`에 맞게 `iteration values`를 반복 생성
// `iteration variables`에 할당한다.

range expressionx라고 했을 때, x는 루프가 시작할 때 딱 한 번 평가된다. 하나의 예외가 있는데, 최대 하나의 이터레이션 변수가 있는 상황에서 len(x) 값이 상수인 경우 range expression은 평가 되지 않는다.

len(x)가 상수인 경우라는 뜻은, x가 상수 문자열이거나, 배열이거나 배열 포인터 이면서, 그 배열에 받는 쪽 채널이나 함수 호출등이 없는 경우에 해당한다. 자세한 내용은 이 링크에 나온다.

range expression을 평가한다는 것은, 어떤 표현식인지를 판단하는 과정이다. 공부하면서 든 생각인데, 슬라이스 중간에 값을 추가하는 동작을 하더라도 이터레이션 횟수가 변하지 않는데, 이유가 표현식을 첫 루프 시작할 때만 평가하기 때문이 아닐까 싶다.


왼쪽에 이터레이션 변수가 있다면, 각 값에 대해 이터레이션 값이 생성된다.

Range Expression 1st value 2nd value
배열, 슬라이스, 배열 포인터 인덱스
문자열 인덱스
채널 요소 -
  1. 배열, 슬라이스, 배열 포인터인 arange expression으로 사용된 경우: 첫 번째 값으로는 인덱스(i) 값이 0부터 오름차순으로 생성된다. 만약 이터레이션 변수가 하나만 제공된 경우, 0부터 len(a) - 1 까지의 값을 생성하게 된다. 두 번째 값으로는 a[i] 값을 생성한다. nil 슬라이스는 이터레이션 횟수가 0이다.
  2. 문자열의 경우, 바이트 인덱스 0부터 시작해서 유니코드 절을 반복한다. 연속 반복할 때 인덱스 값은 문자열에서 UTF-8 인코딩 된 코드 포인트의 첫 번째 바이트 인덱스가 되고, 두 번째 값은 해당 코드 포인트의 값이 된다. 반복 과정 중 잘못된 UTF-8 시퀀스를 만나면, 두 번째 값이 0xFFFD가 되고, 다음 반복에서는 바이트 단위로 진행되게 된다.
  3. map[K]V 타입의 맵 m의 경우, 첫 번째 값은 K 타입인 키 k이다. 두 번째 값은 V 타입인 값 m[k]이다. 반복 순서는 보장되지 않는다. 반복 도중에 도달하지 않은 엔트리가 제거된다면, 이터레이션 값이 생성되지 않는다. 만약 맵의 값이 반복 중 생성되는 경우엔 이터레이션 값을 만들 수도 있고 생략될 수도 있다. 만들어져 있던 엔트리들과 반복마다 다를 수 있다. 만약 맵이 nil인 경우 이터레이션 횟수는 0이다.
  4. 채널의 경우, 이터레이션 값은 채널이 닫히기 까지 받은 연속된 값이다. 만약 채널이 nil이라면, range expression이 영원히 블락되어버린다.

아래는 문자열의 경우 예시이다.

1
2
3
4
5
6
7
8
9
for i, r := range "→👍👎🌮🗂HelloWorld!안녕세상아!😊🚀🔥📝." {
fmt.Print(i, " ")
fmt.Println(string(r))
}

for i, r := range "string, just string" {
fmt.Print(i, " ")
fmt.Println(string(r))
}

아래 문자열을 보면, 룬을 잘라 넣고 이동한 바이트 만큼 인덱스가 이동하는 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
0 →
3 👍
7 👎
11 🌮
15 🗂
19 H
20 e
21 l
22 l
23 o
24 W
25 o
26 r
27 l
28 d
29 !
30 안
33 녕
36 세
39 상
42 아
45 !
46 😊
50 🚀
54 🔥
58 📝
62 .
0 s
1 t
2 r
3 i
4 n
5 g
6 ,
7
8 j
9 u
10 s
11 t
12
13 s
14 t
15 r
16 i
17 n
18 g

다음은 맵에서 값을 추가하는 경우이다.

1
2
3
4
5
6
m := make(map[string]int)
m["key"] = 10
for k, v := range m {
m[k+"next"] = v + 1
fmt.Println(m)
}

아래 결과를 보면, 임의로 추가된 엔트리가 출력되기도 하고, 안되기도 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ go run main. go
map[key:10 keynext:11]

$ go run main.go
map[key:10 keynext:11]
map[key:10 keynext:11 keynextnext:12]

$ go run main.go
map[key:10 keynext:11]
map[key:10 keynext:11 keynextnext:12]
map[key:10 keynext:11 keynextnext:12 keynextnextnext:13]
map[key:10 keynext:11 keynextnext:12 keynextnextnext:13 keynextnextnextnext:14]
map[key:10 keynext:11 keynextnext:12 keynextnextnext:13 keynextnextnextnext:14 keynextnextnextnextnext:15]
map[key:10 keynext:11 keynextnext:12 keynextnextnext:13 keynextnextnextnext:14 keynextnextnextnextnext:15 keynextnextnextnextnextnext:16]
map[key:10 keynext:11 keynextnext:12 keynextnextnext:13 keynextnextnextnext:14 keynextnextnextnextnext:15 keynextnextnextnextnextnext:16 keynextnextnextnextnextnextnext:17]

Reference

Author

changhoi

Posted on

2021-10-18

Updated on

2021-10-18

Licensed under

댓글

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×