레슨 8 / 10·3개 토픽
포인터와 제네릭
포인터 기초
포인터는 값이 저장된 메모리 주소를 담는 변수입니다. &로 변수의 주소를 얻고, *로 포인터가 가리키는 값에 접근(역참조)합니다. Go에서는 포인터 연산이 불가하므로 C보다 안전하게 사용할 수 있습니다.
go
package main
import "fmt"
func main() {
// 포인터 기본
x := 42
p := &x // p는 x의 주소를 가리킴
fmt.Println(*p) // 42 (역참조)
*p = 100 // 포인터를 통해 값 변경
fmt.Println(x) // 100
// nil 포인터
var ptr *int // 초기값은 nil
fmt.Println(ptr == nil) // true
// new()로 포인터 생성
q := new(int) // *int 타입, 제로값(0)으로 초기화
*q = 200
fmt.Println(*q) // 200
}값 리시버 vs 포인터 리시버
go
package main
import "fmt"
type Counter struct {
count int
}
// 값 리시버 — 복사본을 수정 (원본 변경 안 됨)
func (c Counter) ValueIncrement() {
c.count++ // 원본에 영향 없음
}
// 포인터 리시버 — 원본을 직접 수정
func (c *Counter) PointerIncrement() {
c.count++ // 원본 변경됨
}
func (c Counter) GetCount() int {
return c.count
}
func main() {
c := Counter{count: 0}
c.ValueIncrement()
fmt.Println(c.GetCount()) // 0 (변경 안 됨)
c.PointerIncrement()
fmt.Println(c.GetCount()) // 1 (변경됨)
// 함수에 포인터 전달
addTen := func(val *int) {
*val += 10
}
num := 5
addTen(&num)
fmt.Println(num) // 15
}제네릭 (Go 1.18+)
Go 1.18부터 제네릭(타입 매개변수)을 지원합니다. 함수와 타입에 [T constraint] 형태로 타입 매개변수를 선언하며, any(모든 타입)와 comparable(==, != 비교 가능한 타입) 등의 제약 조건을 사용합니다.
go
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// 제네릭 함수 — 모든 숫자 타입의 최솟값
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
// 제네릭 함수 — 슬라이스에서 요소 찾기
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
// 제네릭 타입 — 범용 스택
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
func main() {
fmt.Println(Min(3, 7)) // 3
fmt.Println(Min("apple", "banana")) // apple
fmt.Println(Contains([]int{1, 2, 3}, 2)) // true
fmt.Println(Contains([]string{"a", "b"}, "c")) // false
s := Stack[int]{}
s.Push(10)
s.Push(20)
val, ok := s.Pop()
fmt.Println(val, ok) // 20 true
}- •
&— 변수의 메모리 주소를 얻는 연산자 - •
*— 포인터가 가리키는 값에 접근하는 역참조 연산자 - •
nil— 포인터의 제로 값 (아무것도 가리키지 않음) - •값 리시버 — 구조체의 복사본에서 동작 (원본 변경 불가)
- •포인터 리시버 — 구조체 원본을 직접 수정
- •
[T any]— 모든 타입을 받는 제네릭 타입 매개변수 - •
[T comparable]—==,!=비교 가능한 타입만 허용
💡
구조체가 크거나 메서드에서 필드를 수정해야 한다면 포인터 리시버를 사용하세요. 일관성을 위해 한 타입의 모든 메서드에 같은 리시버 타입(값 또는 포인터)을 사용하는 것이 관례입니다.