Go 언어: 맵, 문자열, 함수, 인터페이스 완벽 정리

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)
}

태그: go map Slice String function

6월 15일 20:15에 게시됨