1. 맵(Map) 심층 분석
Go의 맵은 키-값 쌍을 저장하는 해시 테이블 기반 자료구조입니다.
1.1 맵 초기화와 길이
package map_practice
import "testing"
func TestMapInit(t *testing.T) {
// 리터럴을 사용한 초기화
scores := map[string]int{"Alice": 90, "Bob": 85}
t.Log(scores["Alice"]) // 90 출력
// 빈 맵 생성 후 값 할당
data := map[string]int{}
data["count"] = 100
t.Logf("data 길이: %d", len(data)) // 1 출력
// make()로 초기 용량 지정
cache := make(map[int]string, 20)
t.Logf("cache 길이: %d", len(cache)) // 0 출력 (용량과 무관)
}
1.2 키 존재 여부 확인
맵에서 존재하지 않는 키에 접근하면 해당 타입의 제로 값(zero value)을 반환합니다. 이때 키의 실제 존재 여부를 확인하려면 콤마(comma) 패턴을 사용합니다.
func TestKeyExistence(t *testing.T) {
grades := map[int]int{1: 10, 3: 30}
// 키가 없으면 0 반환
t.Log(grades[5]) // 0 출력
grades[2] = 0
// 키 2는 존재하지만 값이 0
if val, exists := grades[2]; exists {
t.Logf("키 2의 값: %d", val)
} else {
t.Log("키 2 없음")
}
// 키 5는 존재하지 않음
if _, exists := grades[5]; !exists {
t.Log("키 5 없음")
}
}
1.3 맵 순회
func TestMapIteration(t *testing.T) {
users := map[int]string{1: "Kim", 2: "Lee", 3: "Park"}
for id, name := range users {
t.Logf("ID:%d, 이름:%s", id, name)
}
}
2. 맵 응용 패턴
2.1 함수를 값으로 저장
package map_extension
import "testing"
func TestFunctionMap(t *testing.T) {
// 맵의 값으로 함수를 저장
operations := map[string]func(int) int{
"double": func(x int) int { return x * 2 },
"square": func(x int) int { return x * x },
"negate": func(x int) int { return -x },
}
t.Log(operations["double"](5)) // 10
t.Log(operations["square"](5)) // 25
t.Log(operations["negate"](5)) // -5
}
2.2 Set 구현 (맵 활용)
Go에는 기본 Set 자료구조가 없으므로 map[T]bool을 사용하여 구현합니다.
func TestCustomSet(t *testing.T) {
seen := map[int]bool{}
items := []int{1, 2, 3, 2, 1, 4}
for _, item := range items {
seen[item] = true
}
t.Logf("유일한 원소 개수: %d", len(seen)) // 4 출력
// 포함 여부 확인
if seen[2] {
t.Log("2는 있음")
}
// 삭제
delete(seen, 2)
if !seen[2] {
t.Log("2는 없음")
}
}
3. 문자열(String) 다루기
3.1 문자열 분할과 결합
package string_practice
import (
"strings"
"testing"
)
func TestStringManipulation(t *testing.T) {
text := "apple,banana,grape"
fruits := strings.Split(text, ",")
for _, fruit := range fruits {
t.Log(fruit)
}
result := strings.Join(fruits, " | ")
t.Log(result) // "apple | banana | grape"
}
3.2 문자열과 숫자 변환
import (
"strconv"
"testing"
)
func TestConversion(t *testing.T) {
// 정수 → 문자열
numStr := strconv.Itoa(42)
t.Log("값: " + numStr) // "값: 42"
// 문자열 → 정수
if val, err := strconv.Atoi("100"); err == nil {
t.Log(val + 50) // 150
}
}
3.3 UTF-8과 룬(Rune)
func TestUTF8Handling(t *testing.T) {
korean := "안녕하세요"
// len()은 바이트 수를 반환 (UTF-8에서 한글은 3바이트)
t.Logf("바이트 길이: %d", len(korean)) // 15
// rune 슬라이스로 변환하면 글자 수 확인 가능
runes := []rune(korean)
t.Logf("글자 수: %d", len(runes)) // 5
// 각 글자의 유니코드 출력
for i, r := range runes {
t.Logf("인덱스 %d: %c (U+%04X)", i, r, r)
}
}
4. 함수(Function) 고급
4.1 다중 반환값과 함수형 프로그래밍
package function_patterns
import (
"fmt"
"math/rand"
"testing"
"time"
)
// 다중 반환값
func randomPair() (int, int) {
return rand.Intn(100), rand.Intn(100)
}
// 고차 함수: 함수 실행 시간 측정
func measureTime(inner func(int) int) func(int) int {
return func(n int) int {
start := time.Now()
result := inner(n)
elapsed := time.Since(start).Seconds()
fmt.Printf("실행 시간: %.6f초\n", elapsed)
return result
}
}
func slowOp(x int) int {
time.Sleep(500 * time.Millisecond)
return x * x
}
func TestHigherOrderFunction(t *testing.T) {
a, b := randomPair()
t.Logf("랜덤 값: %d, %d", a, b)
timedFunc := measureTime(slowOp)
result := timedFunc(10)
t.Logf("결과: %d", result)
}
4.2 defer와 panic 처리
func TestDeferBehavior(t *testing.T) {
defer func() {
fmt.Println("리소스 정리 완료")
}()
fmt.Println("작업 시작")
// panic이 발생해도 defer는 실행됨
panic("에러 발생!")
}
4.3 가변 인자 함수
func calculateSum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
func TestVariadicFunction(t *testing.T) {
t.Log(calculateSum(1, 2, 3)) // 6
t.Log(calculateSum(10, 20, 30, 40)) // 100
// 슬라이스 전개
values := []int{5, 6, 7}
t.Log(calculateSum(values...)) // 18
}
5. 인터페이스(Interface)와 메서드
5.1 덕 타이핑(Duck Typing) 방식의 인터페이스
package interface_demo
import "testing"
// 인터페이스 정의
type Speaker interface {
SayHello() string
}
// 구조체 정의
type Korean struct{}
// 메서드 구현 (덕 타이핑)
func (k *Korean) SayHello() string {
return "안녕하세요"
}
type English struct{}
func (e *English) SayHello() string {
return "Hello"
}
func TestDuckTyping(t *testing.T) {
var speaker Speaker
speaker = new(Korean)
t.Log(speaker.SayHello()) // "안녕하세요"
speaker = &English{}
t.Log(speaker.SayHello()) // "Hello"
}
5.2 구조체 메서드 정의 (값/포인터 리시버)
package method_patterns
import (
"fmt"
"testing"
"unsafe"
)
type Product struct {
id string
name string
price float64
}
// 값 리시버: 복사본이 전달됨
func (p Product) ShowInfo() string {
fmt.Printf("ShowInfo 주소: %x\n", unsafe.Pointer(&p.name))
return fmt.Sprintf("ID: %s, 이름: %s, 가격: %.2f", p.id, p.name, p.price)
}
// 포인터 리시버: 원본 참조 (메모리 효율적)
func (p *Product) UpdatePrice(newPrice float64) {
p.price = newPrice
}
func TestMethodReceiver(t *testing.T) {
item := Product{"A001", "노트북", 1500000}
fmt.Printf("원본 주소: %x\n", unsafe.Pointer(&item.name))
t.Log(item.ShowInfo())
item.UpdatePrice(1400000)
t.Log(item.ShowInfo()) // 가격이 변경됨
}
6. 구조체 확장과 합성
6.1 명시적 합성 (Composition)
package composition
import (
"fmt"
"testing"
)
type Animal struct{}
func (a *Animal) MakeSound() {
fmt.Println("...(알 수 없는 소리)")
}
func (a *Animal) RespondTo(name string) {
a.MakeSound()
fmt.Printf(" → %s에게 반응\n", name)
}
type Cat struct {
animal Animal
}
func (c *Cat) MakeSound() {
fmt.Println("야옹!")
}
func (c *Cat) RespondTo(name string) {
c.MakeSound()
fmt.Printf(" → %s에게 다가감\n", name)
}
func TestComposition(t *testing.T) {
cat := new(Cat)
cat.RespondTo("주인")
// 출력: 야옹! → 주인에게 다가감
}
6.2 익명 임베딩 (Anonymous Embedding)
type Dog struct {
Animal // 익명 필드
}
func (d *Dog) MakeSound() {
fmt.Println("멍멍!")
}
func TestEmbedding(t *testing.T) {
dog := new(Dog)
dog.RespondTo("산책") // Animal의 메서드 상속
// 주의: Go는 메서드 오버라이딩을 지원하지 않음
// dog.RespondTo() 내부에서는 Animal.MakeSound() 호출
}
7. 다형성(Polymorphism)과 타입 단언
7.1 인터페이스를 통한 다형성
package polymorphism
import (
"fmt"
"testing"
)
type Encoder interface {
Encode() string
}
type JSONEncoder struct{}
func (j *JSONEncoder) Encode() string {
return `{"status": "ok"}`
}
type XMLEncoder struct{}
func (x *XMLEncoder) Encode() string {
return "<status>ok</status>"
}
func processData(enc Encoder) {
fmt.Printf("타입: %T, 결과: %s\n", enc, enc.Encode())
}
func TestPolymorphism(t *testing.T) {
jsonEnc := new(JSONEncoder)
xmlEnc := &XMLEncoder{}
processData(jsonEnc)
processData(xmlEnc)
}
7.2 빈 인터페이스와 타입 스위치
package type_assertion
import (
"fmt"
"testing"
)
func analyzeValue(v interface{}) {
switch val := v.(type) {
case int:
fmt.Printf("정수: %d\n", val)
case string:
fmt.Printf("문자열: %q (길이: %d)\n", val, len(val))
case bool:
fmt.Printf("불리언: %v\n", val)
default:
fmt.Printf("알 수 없는 타입: %T\n", val)
}
}
func TestTypeSwitch(t *testing.T) {
analyzeValue(42)
analyzeValue("Hello, Go!")
analyzeValue(true)
analyzeValue(3.14)
}