🔥 제네릭 타입
618자
7분
Go 언어는 제네릭 함수뿐만 아니라 제네릭 타입도 지원한답니다. 타입 매개변수를 사용해서 타입을 매개변수화할 수 있어요. 이는 제네릭 데이터 구조를 구현할 때 유용하게 쓰일 수 있지요.
다음 예제는 임의의 타입의 값을 저장하는 단일 연결 리스트에 대한 간단한 타입 선언을 보여줍니다.
go
package main
// List는 임의의 타입 T의 값을 저장하는 단일 연결 리스트를 나타냅니다.
type List[T any] struct {
next *List[T] // 다음 노드를 가리키는 포인터
val T // 현재 노드의 값
}
func main() {
}
go
package main
// List는 임의의 타입 T의 값을 저장하는 단일 연결 리스트를 나타냅니다.
type List[T any] struct {
next *List[T] // 다음 노드를 가리키는 포인터
val T // 현재 노드의 값
}
func main() {
}
위 코드에서 List 타입은 타입 매개변수 T를 받아요. T는 any 타입으로 선언되어 있어서, 어떤 타입의 값이라도 저장할 수 있습니다.
List 구조체는 두 개의 필드를 가지고 있어요:
next: 같은 타입의List를 가리키는 포인터입니다. 다음 노드를 가리켜요.val: 타입T의 값을 저장하는 필드입니다. 현재 노드의 값이 되겠죠.
이렇게 제네릭 타입을 사용하면, 여러 타입에 대해 재사용 가능한 데이터 구조를 만들 수 있답니다. 코드 중복을 줄이고 추상화 수준을 높일 수 있어요.
이 리스트 구현에 몇 가지 기능을 추가해 볼게요:
go
// Append 메서드는 리스트 끝에 새 값을 추가합니다.
func (l *List[T]) Append(val T) {
// 리스트가 비어있으면 새 노드를 헤드로 설정
if l.next == nil {
l.next = &List[T]{val: val}
return
}
// 리스트 끝까지 이동
current := l
for current.next != nil {
current = current.next
}
// 새 노드를 끝에 추가
current.next = &List[T]{val: val}
}
// GetAt 메서드는 주어진 인덱스의 값을 반환합니다.
func (l *List[T]) GetAt(index int) (T, bool) {
// 리스트 순회
current := l.next
for i := 0; current != nil; i++ {
if i == index {
return current.val, true // 인덱스 찾음
}
current = current.next
}
var zero T
return zero, false // 인덱스 없음
}
// RemoveAt 메서드는 주어진 인덱스의 노드를 제거합니다.
func (l *List[T]) RemoveAt(index int) bool {
// 리스트가 비어있으면 실패
if l.next == nil {
return false
}
// 헤드 제거
if index == 0 {
l.next = l.next.next
return true
}
// 이전 노드 찾기
current := l
for i := 0; current.next != nil; i++ {
if i == index-1 {
if current.next.next == nil {
current.next = nil // 끝 노드 제거
} else {
current.next = current.next.next // 중간 노드 제거
}
return true
}
current = current.next
}
return false // 인덱스 없음
}
// Print 메서드는 리스트의 모든 값을 출력합니다.
func (l *List[T]) Print() {
current := l.next
for current != nil {
fmt.Printf("%v -> ", current.val)
current = current.next
}
fmt.Println("nil")
}
func main() {
// 정수 리스트 생성
intList := &List[int]{}
// 리스트에 값 추가
intList.Append(1)
intList.Append(2)
intList.Append(3)
// 리스트 출력
fmt.Print("정수 리스트: ")
intList.Print()
// 인덱스 1 위치에 있는 값 출력
fmt.Println(intList.GetAt(1))
// 인덱스 1 위치에 있는 값 삭제
intList.RemoveAt(1)
// 리스트 출력
intList.Print()
// 문자열 리스트 생성
strList := &List[string]{}
// 리스트에 값 추가
strList.Append("Hello")
strList.Append("World")
// 리스트 출력
fmt.Print("문자열 리스트: ")
strList.Print()
}
go
// Append 메서드는 리스트 끝에 새 값을 추가합니다.
func (l *List[T]) Append(val T) {
// 리스트가 비어있으면 새 노드를 헤드로 설정
if l.next == nil {
l.next = &List[T]{val: val}
return
}
// 리스트 끝까지 이동
current := l
for current.next != nil {
current = current.next
}
// 새 노드를 끝에 추가
current.next = &List[T]{val: val}
}
// GetAt 메서드는 주어진 인덱스의 값을 반환합니다.
func (l *List[T]) GetAt(index int) (T, bool) {
// 리스트 순회
current := l.next
for i := 0; current != nil; i++ {
if i == index {
return current.val, true // 인덱스 찾음
}
current = current.next
}
var zero T
return zero, false // 인덱스 없음
}
// RemoveAt 메서드는 주어진 인덱스의 노드를 제거합니다.
func (l *List[T]) RemoveAt(index int) bool {
// 리스트가 비어있으면 실패
if l.next == nil {
return false
}
// 헤드 제거
if index == 0 {
l.next = l.next.next
return true
}
// 이전 노드 찾기
current := l
for i := 0; current.next != nil; i++ {
if i == index-1 {
if current.next.next == nil {
current.next = nil // 끝 노드 제거
} else {
current.next = current.next.next // 중간 노드 제거
}
return true
}
current = current.next
}
return false // 인덱스 없음
}
// Print 메서드는 리스트의 모든 값을 출력합니다.
func (l *List[T]) Print() {
current := l.next
for current != nil {
fmt.Printf("%v -> ", current.val)
current = current.next
}
fmt.Println("nil")
}
func main() {
// 정수 리스트 생성
intList := &List[int]{}
// 리스트에 값 추가
intList.Append(1)
intList.Append(2)
intList.Append(3)
// 리스트 출력
fmt.Print("정수 리스트: ")
intList.Print()
// 인덱스 1 위치에 있는 값 출력
fmt.Println(intList.GetAt(1))
// 인덱스 1 위치에 있는 값 삭제
intList.RemoveAt(1)
// 리스트 출력
intList.Print()
// 문자열 리스트 생성
strList := &List[string]{}
// 리스트에 값 추가
strList.Append("Hello")
strList.Append("World")
// 리스트 출력
fmt.Print("문자열 리스트: ")
strList.Print()
}
Append메서드는 리스트 끝에 새 값을 추가합니다.- 리스트가 비어있으면 새 노드를 헤드로 설정하고,
- 그렇지 않으면 리스트 끝까지 이동한 후 새 노드를 연결하죠.
GetAt메서드는 주어진 인덱스의 값을 반환합니다.- 리스트를 순회하며 해당 인덱스의 노드를 찾아요.
- 인덱스를 찾으면 값과 함께
true를 반환하고, - 찾지 못하면 제로값과
false를 반환합니다.
RemoveAt메서드는 주어진 인덱스의 노드를 제거합니다.- 리스트가 비어있으면 실패하고,
- 인덱스가 0이면 헤드를 다음 노드로 업데이트 하죠.
- 이전 노드를 찾아 연결을 조정하여 노드를 제거해요.
- 성공하면
true, 실패하면false를 반환합니다.
이 코드를 실행하면 다음과 같은 출력을 볼 수 있어요:
text
정수 리스트:
1 -> 2 -> 3 -> nil
2 true
2 -> 3 -> nil
문자열 리스트: Hello -> World -> nil
text
정수 리스트:
1 -> 2 -> 3 -> nil
2 true
2 -> 3 -> nil
문자열 리스트: Hello -> World -> nil
제네릭 타입 List를 사용해서 정수 리스트와 문자열 리스트를 모두 생성하고 사용할 수 있음을 확인할 수 있답니다. 같은 코드로 다양한 타입의 리스트를 다룰 수 있으니 편리하죠? 제네릭을 활용하면 이렇게 코드의 재사용성을 높이고 중복을 줄일 수 있습니다.











