Go 컬렉션
배열
Go 언어에서 배열(Array)은 동일한 데이터 타입의 요소들이 순서대로 저장되는 고정 크기의 컬렉션입니다. 배열은 선언 시 크기를 지정하며, 한 번 선언된 크기는 변경할 수 없습니다.
배열 선언과 초기화
Go 언어에서 배열은 다음과 같이 선언됩니다:
// 배열의 선언
var arr [5]int // 크기가 5인 정수형 배열 선언
// 배열의 초기화
arr = [5]int{1, 2, 3, 4, 5} // 배열 요소 초기화
// 배열 요소에 접근
fmt.Println(arr[0]) // 첫 번째 요소에 접근하여 출력
위 예제에서 arr은 크기가 5인 정수형 배열을 선언하고 초기화하고 있습니다. 배열 요소에 접근할 때는 인덱스를 사용하여 접근합니다. 인덱스는 0부터 시작하며, 범위를 벗어나는 인덱스로 접근하려 하면 런타임 에러가 발생할 수 있습니다.
배열 길이와 용량
배열의 길이(length)는 배열에 실제로 저장된 요소의 수를 나타내며, 배열의 용량(capacity)은 배열이 가질 수 있는 최대 요소 수를 나타냅니다. Go 언어에서 배열의 길이와 용량은 동일합니다.
var arr [5]int // 길이가 5인 배열 선언
fmt.Println(len(arr)) // 배열의 길이 출력, 출력: 5
배열의 초기화 방법
Go 언어에서 배열을 초기화하는 방법에는 몇 가지가 있습니다:
- 리터럴 초기화: 배열 선언과 동시에 값을 할당합니다.
var arr = [5]int{1, 2, 3, 4, 5}
- 생략된 길이: 배열 길이를 생략하고 초기화할 값을 지정하면 Go 언어가 자동으로 배열의 길이를 계산합니다.
var arr = [...]int{1, 2, 3, 4, 5} // 배열의 길이는 자동으로 5로 설정됨 - 특정 인덱스 초기화: 특정 인덱스에만 값을 할당하고 나머지는 기본값으로 초기화됩니다.
var arr [5]int
arr[0] = 1
arr[3] = 4
배열의 반복문 처리
배열의 모든 요소에 접근하기 위해 for 반복문을 사용할 수 있습니다:
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
또는 range 키워드를 사용하여 인덱스와 요소 값을 반복할 수 있습니다:
for index, value := range arr {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
배열의 특징
- 고정된 크기: 배열은 선언할 때 지정한 크기만큼 고정됩니다. 크기를 변경할 수 없습니다.
- 값 복사: 배열은 값 타입이므로 배열을 다른 변수에 할당하면 배열 전체가 복사됩니다.
- 성능: 배열은 인덱스로 직접 접근할 수 있어 성능이 좋습니다.
배열 vs 슬라이스
배열과 유사하지만 크기가 동적으로 조정될 수 있는 슬라이스(Slice)는 Go 언어에서 보다 유연한 컬렉션 타입입니다. 슬라이스는 배열 위에 추상화된 뷰를 제공하며, 동적으로 크기를 조정할 수 있습니다.
var slice []int // 슬라이스 선언
slice = arr[1:4] // 배열 arr의 인덱스 1부터 3까지 슬라이스 생성
따라서 배열은 크기가 고정되어 있고 슬라이스는 동적으로 조정할 수 있는 차이점이 있습니다. Go 언어에서는 슬라이스를 더 많이 사용하여 유연하게 데이터를 관리하는 것이 일반적입니다.
슬라이스(slice)
Go 언어에서 슬라이스 (Slice)는 배열(Array) 위에 구조체로 구현된 동적 배열(dynamic array)입니다. 슬라이스는 배열의 일부분을 가리키며, 배열보다 더 유연하게 데이터를 관리할 수 있는 중요한 데이터 구조입니다.
슬라이스의 특징
- 동적 크기: 슬라이스는 동적으로 크기를 조정할 수 있습니다. 배열과 달리 선언 시 크기를 지정하지 않습니다.
- 참조 타입: 슬라이스는 참조 타입(reference type)이며, 내부적으로 배열을 가리키는 구조체입니다. 이는 슬라이스를 함수로 전달할 때 배열의 복사가 아니라 참조가 전달된다는 의미입니다.
- 구조: 슬라이스는 세 가지 요소로 구성됩니다:
- 포인터(pointer): 배열의 시작을 가리키는 포인터
- 길이(length): 슬라이스에 포함된 요소의 수
- 용량(capacity): 슬라이스가 할당된 메모리 공간에서 사용 가능한 최대 요소 수
슬라이스의 선언과 초기화
슬라이스는 배열과 유사하게 초기화할 수 있습니다. 다만 크기를 생략하고 중괄호 내에 값을 지정하면 슬라이스가 자동으로 크기가 결정됩니다:
// 슬라이스 선언
var slice []int
// 슬라이스 초기화
slice = []int{1, 2, 3, 4, 5}
또는 한 줄로 선언과 초기화를 동시에 할 수 있습니다:
slice := []int{1, 2, 3, 4, 5}
슬라이스의 길이와 용량
슬라이스의 길이는 현재 포함된 요소의 개수를 나타내며, 용량은 슬라이스가 할당된 메모리 공간에서 추가 요소를 저장할 수 있는 최대 개수를 나타냅니다. 슬라이스의 길이와 용량을 확인하기 위해 len 함수와 cap 함수를 사용할 수 있습니다:
slice := []int{1, 2, 3, 4, 5}
fmt.Println("Length of slice:", len(slice)) // 출력: 5
fmt.Println("Capacity of slice:", cap(slice)) // 출력: 5 (기본적으로 길이와 동일)
슬라이스의 조작
슬라이스는 배열과 달리 동적으로 크기를 조정할 수 있습니다. 다음은 슬라이스의 주요 조작 방법입니다:
- 슬라이싱: 부분 슬라이스를 생성할 수 있습니다.
slice := []int{1, 2, 3, 4, 5}
subSlice := slice[1:4] // 인덱스 1부터 3까지의 부분 슬라이스 - 요소 추가: 내장 함수 append를 사용하여 요소를 슬라이스에 추가할 수 있습니다.
slice := []int{1, 2, 3}
slice = append(slice, 4, 5) // 슬라이스에 4와 5 추가 - 요소 제거: 슬라이스의 특정 위치에 있는 요소를 제거할 수는 없지만, 요소를 제외한 새로운 슬라이스를 생성하여 구현할 수 있습니다.
- 용량 확장: append 함수를 사용하여 슬라이스의 용량을 확장할 수 있습니다. 슬라이스의 길이가 용량을 초과하면 Go 언어는 더 큰 용량의 새로운 배열을 할당하고 기존 요소를 복사하여 새로운 배열에 저장합니다.
슬라이스와 배열의 차이점
- 크기 변경: 배열은 선언 시 크기가 고정되지만, 슬라이스는 동적으로 크기를 조정할 수 있습니다.
- 전달 방식: 배열은 값 타입으로 전달되어 함수에 전달될 때 복사됩니다. 반면 슬라이스는 참조 타입으로 전달되어 함수에 의해 공유됩니다.
- 사용 상황: 슬라이스는 동적인 데이터 처리와 메모리 관리에 유리하며, 배열은 고정 크기 데이터 구조에 적합합니다.
슬라이스는 Go 언어에서 매우 중요한 데이터 구조로, 대부분의 컬렉션 데이터 처리에 사용됩니다. 배열보다 유연하고 효율적인 메모리 관리를 가능하게 해 주며, 함수형 프로그래밍 스타일에서도 유용하게 사용됩니다.
맵(Map)
Go 언어의 맵(Map)은 키(key)와 값(value)의 쌍으로 데이터를 저장하는 자료구조입니다. 맵은 해시 테이블(hash table)을 기반으로 구현되어 있어, 키를 기준으로 빠르게 데이터를 검색, 삽입, 삭제할 수 있는 특성을 가지고 있습니다.
맵의 선언과 초기화
맵은 make 함수를 사용하여 초기화하거나 리터럴 형태로 선언하고 초기화할 수 있습니다:
// 맵의 선언 및 초기화
var m map[keyType]valueType
m = make(map[keyType]valueType)
// 또는 짧은 선언 방식
m := make(map[keyType]valueType)
// 맵 리터럴을 사용한 초기화
m := map[keyType]valueType{
key1: value1,
key2: value2,
//...
}
여기서 keyType는 키의 데이터 타입을, valueType는 값의 데이터 타입을 나타냅니다. 맵은 내장 함수인 make를 사용하여 초기화할 수도 있고, 짧은 선언 방식으로도 초기화할 수 있습니다. 맵 리터럴을 사용하면 선언과 초기화를 동시에 할 수 있습니다.
맵의 사용 예시
package main
import "fmt"
func main() {
// 맵 초기화
ages := map[string]int{
"Alice": 31,
"Bob": 34,
"Charlie": 28,
}
// 요소 추가
ages["David"] = 23
// 요소 조회
fmt.Println("Alice's age is", ages["Alice"])
// 요소 삭제
delete(ages, "Bob")
// 키 체크
if age, ok := ages["Charlie"]; ok {
fmt.Println("Charlie's age is", age)
} else {
fmt.Println("Charlie's age is not present")
}
// 맵 순회
for name, age := range ages {
fmt.Printf("%s's age is %d\n", name, age)
}
}
- 요소 추가 및 갱신: 특정 키에 대한 값을 할당하여 요소를 추가하거나 갱신할 수 있습니다.
ages["Eve"] = 25 // 추가
ages["Alice"] = 32 // 갱신 - 요소 삭제: delete 내장 함수를 사용하여 맵에서 특정 키와 그에 해당하는 값을 삭제할 수 있습니다.
delete(ages, "Bob") - 키 체크: 맵에서 특정 키가 존재하는지 확인하고, 그 값에 접근할 수 있습니다.
age, ok := ages["Alice"]
if ok {
fmt.Println("Alice's age is", age)
} else {
fmt.Println("Alice's age is not present")
} - 맵 순회: for range 구문을 사용하여 맵의 모든 요소를 순회할 수 있습니다.
for name, age := range ages {
fmt.Printf("%s's age is %d\n", name, age)
}
맵의 특성
- 순서 보장 안 함: 맵은 요소의 순서를 보장하지 않습니다. 맵에 추가한 순서대로 요소가 저장되지 않고, 순회할 때마다 순서가 다를 수 있습니다.
- 키의 유일성: 맵은 각 키는 유일해야 하며, 동일한 키를 가진 요소가 존재할 수 없습니다. 키는 값의 주소 역할을 합니다.
- Nil 맵: 맵은 nil로 초기화될 수 있으며, nil 맵에 요소를 추가하거나 삭제하려 하면 런타임 에러가 발생합니다.
- 참조 타입: 맵은 참조 타입이므로 함수로 전달할 때는 맵의 복사본이 아니라 맵의 참조가 전달됩니다. 이는 메모리 사용 측면에서 유리하지만, 주의해서 사용해야 합니다.
맵은 매우 유연하고 효율적인 데이터 구조로, 데이터를 키-값 쌍으로 관리하고 검색하는 데 매우 유용합니다. Go 언어에서는 맵을 자주 사용하여 데이터를 구조화하고 효율적으로 관리하는 것이 일반적입니다.
Go 언어에서 맵(Map)에서 요소를 조회할 때 사용되는 문법 중 하나는 ok 패턴입니다. ok는 불리언(boolean) 타입의 변수로, 맵에서 특정 키에 대한 값이 존재하는지 여부를 나타냅니다. 이 패턴은 맵에서 요소를 안전하게 조회하고, 요소의 존재 여부를 확인하는 데 사용됩니다.
ok 패턴 설명
맵에서 특정 키에 대한 값을 조회할 때, 맵이 그 키를 가지고 있는지 여부를 ok 패턴을 통해 확인할 수 있습니다. ok 패턴은 다음과 같은 문법을 가집니다:
value, ok := map[key]
여기서:
- value: 맵에서 특정 키에 대한 값입니다.
- ok: 불리언(boolean) 타입의 변수로, 키가 맵에 존재하면 true, 존재하지 않으면 false가 할당됩니다.
예시
다음은 ok 패턴을 사용하여 맵에서 특정 키에 대한 값을 안전하게 조회하는 예시입니다:
package main
import "fmt"
func main() {
ages := map[string]int{
"Alice": 31,
"Bob": 34,
"Charlie": 28,
}
// Alice 키의 값 조회
if age, ok := ages["Alice"]; ok {
fmt.Println("Alice's age is", age)
} else {
fmt.Println("Alice's age is not present")
}
// Eve 키의 값 조회 (Eve는 존재하지 않는 경우)
if age, ok := ages["Eve"]; ok {
fmt.Println("Eve's age is", age)
} else {
fmt.Println("Eve's age is not present")
}
}
위 예시에서는 ages 맵에서 Alice와 Eve 키의 값을 조회하고 있습니다.
- ages["Alice"] 조회 시, 맵에 Alice 키가 존재하므로 ok는 true가 되고, age 변수에는 31이 할당됩니다. 따라서 "Alice's age is 31"이 출력됩니다.
- ages["Eve"] 조회 시, 맵에 Eve 키가 존재하지 않으므로 ok는 false가 되고, age 변수는 0 값 또는 해당 타입의 제로 값이 할당됩니다. 따라서 "Eve's age is not present"가 출력됩니다.
이처럼 ok 패턴을 사용하면 맵에서 요소를 조회할 때 맵에 키가 존재하는지 먼저 확인하고, 안전하게 값을 가져올 수 있습니다. 이는 예상치 못한 런타임 에러를 방지하고 코드의 안정성을 높이는 데 유용합니다.
GIT : https://github.com/kkaok/study-golang/tree/master/src/example/array
https://github.com/kkaok/study-golang/tree/master/src/example/slice
https://github.com/kkaok/study-golang/tree/master/src/example/map
출처 : chatGPT