Go에서 range의 모든 것
Go 언어의 내장 함수들은 많지 않지만, 각각이 일당백 역할을 한다. make
, new
, range
는 Go 프로그램을 작성할 때 넓은 범위에서 사용되는 내장 함수이다. 이번 포스팅에서는 range
활용에 대해서 정리해두었다.
range
는 for
와 함께 사용된다. range
의 뒷 부분으로 배열, 슬라이스, 맵, 또는 채널(특히, 값을 받는 역할을 하는)이 들어올 수 있다. 이터레이션 값들 (iteration values
라고 표현함)이 있는 경우 그 값을 할당해주고 반복문의 블록으로 진입하게 된다.
1 | for i, val := range intSlice { |
range
문의 오른쪽 영역을 range expression
이라고 하는데, 이 영역에는 다음 표현들이 들어올 수 있다.
- 배열, 배열 포인터
- 슬라이스
- 문자열
- 맵
- 채널 (받기 동작을 할 수 있어야 한다.)
range
왼쪽의 =
또는 :=
기준으로 그 다음 왼쪽 영역은 이터레이션 변수(iteration variables
)라고 하는데, range expression
이 채널인 경우는 최대 1개의 이터레이션 변수가 가능하고, 나머지는 최대 2개까지 가능하다.
0개 부터 2개까지 사용이 가능하다, 1개만 사용하면, 첫 번째 이터레이션 값이 할당된다.
1 | for [`iteration variables`] := range `range expression` |
range expression
를 x
라고 했을 때, x
는 루프가 시작할 때 딱 한 번 평가된다. 하나의 예외가 있는데, 최대 하나의 이터레이션 변수가 있는 상황에서 len(x)
값이 상수인 경우 range expression
은 평가 되지 않는다.
len(x)
가 상수인 경우라는 뜻은,x
가 상수 문자열이거나, 배열이거나 배열 포인터 이면서, 그 배열에 받는 쪽 채널이나 함수 호출등이 없는 경우에 해당한다. 자세한 내용은 이 링크에 나온다.
range expression
을 평가한다는 것은, 어떤 표현식인지를 판단하는 과정이다. 공부하면서 든 생각인데, 슬라이스 중간에 값을 추가하는 동작을 하더라도 이터레이션 횟수가 변하지 않는데, 이유가 표현식을 첫 루프 시작할 때만 평가하기 때문이 아닐까 싶다.
왼쪽에 이터레이션 변수가 있다면, 각 값에 대해 이터레이션 값이 생성된다.
Range Expression | 1st value | 2nd value |
---|---|---|
배열, 슬라이스, 배열 포인터 | 인덱스 | 값 |
문자열 | 인덱스 | 룬 |
맵 | 키 | 값 |
채널 | 요소 | - |
- 배열, 슬라이스, 배열 포인터인
a
가range expression
으로 사용된 경우: 첫 번째 값으로는 인덱스(i
) 값이 0부터 오름차순으로 생성된다. 만약 이터레이션 변수가 하나만 제공된 경우, 0부터len(a) - 1
까지의 값을 생성하게 된다. 두 번째 값으로는a[i]
값을 생성한다.nil
슬라이스는 이터레이션 횟수가 0이다. - 문자열의 경우, 바이트 인덱스 0부터 시작해서 유니코드 절을 반복한다. 연속 반복할 때 인덱스 값은 문자열에서 UTF-8 인코딩 된 코드 포인트의 첫 번째 바이트 인덱스가 되고, 두 번째 값은 해당 코드 포인트의 값이 된다. 반복 과정 중 잘못된 UTF-8 시퀀스를 만나면, 두 번째 값이
0xFFFD
가 되고, 다음 반복에서는 바이트 단위로 진행되게 된다. map[K]V
타입의 맵m
의 경우, 첫 번째 값은K
타입인 키k
이다. 두 번째 값은V
타입인 값m[k]
이다. 반복 순서는 보장되지 않는다. 반복 도중에 도달하지 않은 엔트리가 제거된다면, 이터레이션 값이 생성되지 않는다. 만약 맵의 값이 반복 중 생성되는 경우엔 이터레이션 값을 만들 수도 있고 생략될 수도 있다. 만들어져 있던 엔트리들과 반복마다 다를 수 있다. 만약 맵이nil
인 경우 이터레이션 횟수는 0이다.- 채널의 경우, 이터레이션 값은 채널이 닫히기 까지 받은 연속된 값이다. 만약 채널이
nil
이라면,range expression
이 영원히 블락되어버린다.
아래는 문자열의 경우 예시이다.
1 | for i, r := range "→👍👎🌮🗂HelloWorld!안녕세상아!😊🚀🔥📝." { |
아래 문자열을 보면, 룬을 잘라 넣고 이동한 바이트 만큼 인덱스가 이동하는 것을 확인할 수 있다.
1 | 0 → |
다음은 맵에서 값을 추가하는 경우이다.
1 | m := make(map[string]int) |
아래 결과를 보면, 임의로 추가된 엔트리가 출력되기도 하고, 안되기도 한다.
1 | $ go run main. go |