728x90

Go 언어에서 메서드를 정의할 때, 구조체와 메서드의 관계를 결정하는 중요한 요소 중 하나는 리시버(Receiver)의 타입임

리시버는 메서드가 호출될 때 어떤 방식으로 구조체 데이터를 다룰지 결정하며, 값 리시버포인터 리시버 두 가지 방식이 있음

값 리시버란?

값 리시버는 메서드 호출 시, 구조체의 복사본을 리시버로 받는 방식

즉, 메서드가 호출될 때 원본 구조체가 아닌, 그 복사된 값이 메서드로 전달됨

package main

import "fmt"

type Counter struct {
    count int
}

func (c Counter) Increment() {
    c.count++
}

func (c Counter) GetCount() int {
    return c.count
}

func main() {
    counter := Counter{}
    counter.Increment()
    fmt.Println(counter.GetCount())  // 출력: 0
}

위 코드에서 Increment() 메서드는 값 리시버를 사용함

메서드가 호출될 때 Counter 구조체의 복사본이 전달되기 때문에, Increment()가 count 값을 증가시키더라도 원본 구조체의 값은 변하지 않음

따라서 GetCount() 메서드로 값을 확인하면 여전히 0이 출력됨

Increment 메서드는 아무런 의미가 없는 메서드라고 볼 수 있음

포인터 리시버란?

포인터 리시버는 메서드 호출 시, 구조체의 메모리 주소를 전달받아 원본 데이터를 직접 수정할 수 있는 방식

포인터 리시버를 사용하면 메서드 내부에서 구조체의 상태를 변경할 수 있으며, 메모리 주소를 다루기 때문에 큰 구조체를 복사하지 않고도 성능을 최적화할 수 있음

package main

import "fmt"

type Counter struct {
    count int
}

func (c *Counter) Increment() {
    c.count++
}

func (c *Counter) GetCount() int {
    return c.count
}

func main() {
    counter := Counter{}
    counter.Increment()
    fmt.Println(counter.GetCount())  // 출력: 1
}

위 코드에서 Increment() 메서드는 포인터 리시버를 사용함

이 메서드는 구조체의 포인터를 전달받아, 원본 구조체의 count 값을 직접 수정할 수 있음

따라서 Increment() 메서드를 호출한 후 GetCount() 메서드를 통해 수정된 값을 확인할 수 있으며, 출력은 1이 됨

값 리시버와 포인터 리시버의 차이점

구분 값 리시버 포인터 리시버
구조체 전달 방식 구조체의 복사본 전달 구조체의 메모리 주소 전달
원본 수정 여부 원본 수정 불가능 원본 수정 가능
메모리 효율성 구조체가 클 경우 비효율적 (복사 비용) 메모리 효율적
상태 변경 메서드 내에서 상태를 변경할 수 없음 메서드 내에서 상태를 변경할 수 있음

성능 차이

값 리시버는 구조체의 복사본을 사용하기 때문에, 큰 구조체에서는 메모리 복사로 인해 성능 저하가 발생할 수 있음

반면, 포인터 리시버는 구조체의 메모리 주소를 사용하므로 성능적으로 더 효율적

상태 변경

값 리시버는 구조체의 상태를 변경하지 않음

즉, 복사본에서만 변경이 일어나므로 원래의 구조체 상태에는 영향을 미치지 않음

반면, 포인터 리시버는 원본 구조체의 데이터를 직접 수정할 수 있어 상태 변경이 필요한 경우 적합함

언제 값 리시버를 사용하고, 언제 포인터 리시버를 사용해야 할까

  • 값 리시버가 유리한 경우
    • 구조체가 작고, 복사 비용이 적으며, 동시성 문제를 피해야 하는 경우 값 리시버가 유리함
    • 값 리시버는 복사본을 사용하기 때문에 원본 데이터의 상태를 변경할 수 없으며, 여러 고루틴이 안전하게 동시 접근할 수 있음
  • 포인터 리시버가 유리한 경우
    • 구조체가 크거나, 원본 상태를 변경할 필요가 있는 경우 포인터 리시버가 유리함
    • 그러나 동시성 문제를 신경 써야 하며, 성능 이점을 얻으려면 구조체 크기와 복사 비용을 고려해야 함

값 리시버와 포인터 리시버를 모두 사용하는 예제

하나의 구조체에 값 리시버와 포인터 리시버를 모두 사용할 수 있음

package main

import "fmt"

type Person struct {
    name string
    age  int
}

// 값 리시버: 상태를 변경하지 않음
func (p Person) GetName() string {
    return p.name
}

// 포인터 리시버: 상태를 변경함
func (p *Person) HaveBirthday() {
    p.age++
}

func main() {
    person := Person{name: "Alice", age: 25}

    fmt.Println("이름:", person.GetName()) // 값 리시버 사용
    person.HaveBirthday()                 // 포인터 리시버 사용
    fmt.Println("나이:", person.age)       // 출력: 26
}

GetName() 메서드는 값 리시버를 사용하여 데이터를 읽기만 하며, HaveBirthday() 메서드는 포인터 리시버를 사용하여 age 필드를 직접 수정함

 

 

728x90

'Go' 카테고리의 다른 글

Go 함수와 메서드 차이  (0) 2024.06.24
Go 구조체, 메서드, 인터페이스  (0) 2024.06.19
728x90

 

Go 언어에서 함수와 메서드는 코드 구조화와 재사용성에 중요한 역할을 함

Go 언어에서 함수와 메서드는 각각의 용도와 목적이 다름

독립 함수는 새로운 인스턴스를 생성하는 데 주로 사용되며, 인스턴스 메서드는 기존 인스턴스를 수정하거나 조작하는 데 사용됨

독립 함수 (Standalone Function)

  • 정의: 특정 구조체에 속하지 않는 독립적인 함수
  • 용도: 주로 새로운 인스턴스를 생성하고 반환하는 역할을 함
package main

import "fmt"

type Config struct {
    Port int
}

type Server struct {
    config Config
}

// 독립 함수: 새로운 Server 인스턴스를 생성하고 반환
func NewServer(config Config) *Server {
    return &Server{
        config: config,
    }
}

func main() {
    config := Config{Port: 8080}
    server := NewServer(config)
    fmt.Println("Server running on port:", server.config.Port)
}

인스턴스 메서드 (Instance Method)

  • 정의: 특정 구조체의 인스턴스로 호출되는 메서드
  • 용도: 주로 기존 인스턴스를 수정하거나, 해당 인스턴스와 관련된 동작을 수행함
package main

import "fmt"

type Config struct {
    Port int
}

type Server struct {
    config Config
}

// 인스턴스 메서드: 기존 Server 인스턴스를 수정하고 반환
func (server *Server) NewServer(config Config) *Server {
    server.config = config
    return server
}

func main() {
    server := &Server{}
    config := Config{Port: 8080}
    server = server.NewServer(config)
    fmt.Println("Server running on port:", server.config.Port)
}

주요 차이점

  • 소속:
    • 독립 함수: 구조체와 독립적으로 정의됨
    • 인스턴스 메서드: 특정 구조체의 리시버를 가짐
  • 역할:
    • 독립 함수: 새로운 인스턴스를 생성
    • 인스턴스 메서드: 기존 인스턴스를 수정하거나 동작 수행
  • 리시버 사용:
    • 독립 함수: 리시버 없음
    • 인스턴스 메서드: 리시버를 통해 호출됨
728x90
728x90

구조체

여러 필드를 하나로 묶어 새로운 타입을 정의할 수 있는 방법 (ex: 개 > 이름, 나이, ...)

메서드

특정 타입에 속한 함수 (개 > 짖기, ...)

메서드를 정의하면 해당 타입의 인스턴스에서 호출할 수 있음

인터페이스

메서드 시그니처의 집합으로, 어떤 타입이 특정 인터페이스를 구현하려면 그 인터페이스에 정의된 모든 메서드를 구현해야 함

(인터페이스 : 동물 > 짖기 => 구조체 : 강아지 > 짖기, 고양이 > 짖기)

예시

package main

import "fmt"

// 인터페이스 정의
type Animal interface {
    Speak() string
}

// Dog 구조체 정의
type Dog struct {
    Name string
    Age int
}

// Dog에 Speak 메서드 구현
func (d Dog) Speak() string {
    return "Woof!"
}

// Cat 구조체 정의
type Cat struct {
    Name string
}

// Cat에 Speak 메서드 구현
func (c Cat) Speak() string {
    return "Meow!"
}

func main() {
    // Animal 인터페이스 타입의 슬라이스 생성
    animals := []Animal{
        Dog{Name: "Rex", Age: 5},
        Cat{Name: "Whiskers"},
    }

    // 각 동물의 Speak 메서드 호출
    for _, animal := range animals {
        fmt.Println(animal.Speak())
    }
}

 

 

 

728x90

'Go' 카테고리의 다른 글

Go 구조체 메서드의 값 리시버와 포인터 리시버  (1) 2024.09.13
Go 함수와 메서드 차이  (0) 2024.06.24

+ Recent posts