Go

Go 언어 포인터 완벽 해설

드리프트2 2024. 10. 6. 21:28

Go 언어 포인터 완벽 해설


서론

안녕하세요, 여러분!

 

오늘은 Go 언어에서 중요한 개념 중 하나인 포인터에 대해 깊이 있게 다뤄볼게요.

 

포인터는 처음 접할 때 조금 헷갈릴 수 있지만, 이해하면 Go를 더욱 효과적으로 활용할 수 있는 강력한 도구입니다.

 

그럼 함께 알아볼까요?

 

포인터란 무엇인가요?

포인터는 변수의 메모리 주소를 저장하는 특별한 변수입니다.

 

쉽게 말해, 포인터는 다른 변수의 위치를 가리키는 역할을 합니다.

 

이를 통해 변수의 값을 직접 수정하거나 함수 간에 데이터를 효율적으로 전달할 수 있습니다.

 

package main

import (
    "fmt"
)

func main() {
    var number int = 42
    var ptr *int = &number

    fmt.Println("number의 값:", number)    // number의 값: 42
    fmt.Println("number의 주소:", &number) // number의 주소: 0xc0000140a8
    fmt.Println("ptr이 가리키는 값:", *ptr) // ptr이 가리키는 값: 42
}

 

포인터의 기본 연산자

 

Go에서 포인터를 다룰 때 주로 사용하는 두 가지 연산자가 있어요: &*입니다.

 

  • & (주소 연산자): 변수의 메모리 주소를 가져올 때 사용해요.
  • * (간접 참조 연산자): 포인터가 가리키는 메모리 주소의 값을 가져올 때 사용해요.
package main

import (
    "fmt"
)

func main() {
    var a int = 100
    var p *int = &a

    fmt.Println("a의 값:", a)      // a의 값: 100
    fmt.Println("a의 주소:", &a)   // a의 주소: 0xc0000140a8
    fmt.Println("p의 값:", p)      // p의 값: 0xc0000140a8
    fmt.Println("*p의 값:", *p)    // *p의 값: 100

    *p = 200
    fmt.Println("a의 새로운 값:", a) // a의 새로운 값: 200
}

 

포인터의 활용 사례

 

포인터는 다양한 상황에서 유용하게 사용됩니다.

 

몇 가지 대표적인 활용 사례를 살펴볼까요?

 

1. 함수에서 값 변경하기

기본적으로 Go는 값 복사 방식으로 함수에 인자를 전달합니다.

 

하지만 포인터를 사용하면 함수 내에서 원본 데이터를 수정할 수 있어요.

package main

import (
    "fmt"
)

func increment(x *int) {
    *x += 1
}

func main() {
    a := 10
    increment(&a)
    fmt.Println("a의 값:", a) // a의 값: 11
}

 

2. 메모리 효율성

 

큰 데이터를 함수에 전달할 때 포인터를 사용하면 메모리를 절약할 수 있습니다.

 

데이터의 복사가 아닌 주소만 전달되기 때문이죠.

package main

import (
    "fmt"
)

type LargeStruct struct {
    data [1000]int
}

func process(ls *LargeStruct) {
    ls.data[0] = 1
}

func main() {
    var ls LargeStruct
    process(&ls)
    fmt.Println("ls.data[0]:", ls.data[0]) // ls.data[0]: 1
}

 

포인터와 구조체

 

구조체와 포인터를 함께 사용하면 더욱 효율적인 코드 작성을 할 수 있습니다.

package main

import (
    "fmt"
)

type Person struct {
    name string
    age  int
}

func main() {
    p := Person{name: "Alice", age: 30}
    fmt.Println("초기값:", p) // 초기값: {Alice 30}

    updateAge(&p, 31)
    fmt.Println("업데이트된 값:", p) // 업데이트된 값: {Alice 31}
}

func updateAge(p *Person, newAge int) {
    p.age = newAge
}

 

포인터의 주의사항

 

포인터는 강력하지만, 몇 가지 주의해야 할 점도 있습니다.

 

  1. nil 포인터: 포인터가 어떤 주소도 가리키지 않을 때 nil입니다. nil 포인터를 역참조하면 런타임 에러가 발생합니다.
  2. package main import ( "fmt" ) func main() { var p *int // fmt.Println(*p) // 런타임 에러: invalid memory address or nil pointer dereference }
  3. 포인터 연산 제한: Go는 안전성을 위해 포인터 연산을 제한합니다. 포인터를 이용한 산술 연산은 허용되지 않아요.

 

포인터의 고급 개념

 

1. 포인터의 포인터

포인터 자체도 변수이기 때문에, 포인터를 가리키는 포인터를 만들 수 있습니다.

package main

import (
    "fmt"
)

func main() {
    a := 50
    p := &a
    pp := &p

    fmt.Println("a의 값:", a)       // a의 값: 50
    fmt.Println("p의 값:", p)       // p의 값: 0xc0000140a8
    fmt.Println("*p의 값:", *p)     // *p의 값: 50
    fmt.Println("pp의 값:", pp)     // pp의 값: 0xc00000a0b8
    fmt.Println("**pp의 값:", **pp) // **pp의 값: 50
}

 

2. nil과 포인터

 

포인터를 사용할 때 nil을 명시적으로 처리하는 방법을 알아두면 좋습니다.

package main

import (
    "fmt"
)

func main() {
    var p *int = nil

    if p != nil {
        fmt.Println(*p)
    } else {
        fmt.Println("포인터가 nil입니다.") // 포인터가 nil입니다.
    }
}

 

결론

포인터는 Go 언어에서 매우 중요한 개념으로, 메모리 관리와 효율적인 데이터 처리를 가능하게 합니다.

 

초반에는 다소 복잡해 보일 수 있지만, 여러 예제를 통해 익숙해지면 Go 프로그래밍을 더욱 깊이 있게 이해할 수 있습니다.

 

오늘 배운 내용을 바탕으로 다양한 프로젝트에 포인터를 적극 활용해보세요!