본문 바로가기

Language/Go

[기초] Go 기초 정리 - 4 (Method)

Tour of Go 기반으로한 Go 기본 내용입니다.

 

Methods

기본적으로 Go언어는 클래스를 가지지 않음.

하지만 비슷하게 타입의 메서드를 정의할 수 있다.

메서드는 특별한 receiver인자가 있는 함수인데, func 키워드와 메서드 이름 사이에 위치하고 있다.

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}
//Vertex 구조체 타입에 Abs 메서드
func (v Vertex) Abs() float64 { 
	//func와 함수명 사이에 리시버가 위치하고있다.
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}

- 메서드는 리시버 인수가 있는 함수이다!

 

package main

import (
	"fmt"
	"math"
)

type MyFloat float64 //구조체가 아닌 타입

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f)
	fmt.Println(f.Abs())
}

- 구조체가 아닌 형식에 대해서도 메소드를 선언할 수 있다.

- 메소드와 동일한 패키지에 수신자가 정의되어 있어야 함.

- 타입이 다른 패키지에 정의된 경우(int포함)에는 해당 타입을 리시버로 하여 메서드 정의 불가!

 

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())
}
/*
결과
======
50
======
*/

- 포인터 리시버로 메서드를 선언할 수 있음.

- 위 예시에서 만약 Scale 메서드의 리시버를 Vertex로 변경한다면 10배로 scale된 결과를 얻을 수 없었을 것!

 

package main

import "fmt"

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(2) //편의상 (&v).Scale(2)로 알아서 변환!
	ScaleFunc(&v, 10)

	p := &Vertex{4, 3}
	p.Scale(3)
	ScaleFunc(p, 8)

	fmt.Println(v, p)
}
/*
결과
======
{60 80} &{96 72}
======

- 위 예시에서 v 문장의 경우, v.Scale(2)는 v가 포인터가 아니더라도 포인터 리시버가 있는 메서드가 자동으로 호출.

- v.Scale(2)를 Go에서 알아서 (&v).Scale(2)로 해석해줌.

- 아래 예시는 그 반대도 가능함을 보여주고 있다.

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func AbsFunc(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
	fmt.Println(AbsFunc(v))

	p := &Vertex{4, 3}
	fmt.Println(p.Abs()) //(*p).Abs()로 해석
	fmt.Println(AbsFunc(*p))
}

 

- p의 경우, Go에서 (*p).Abs()로 해석해준다.

 

값 또는 포인터 리시버 선택

- 값 리시버 : 메서드를 통해 호출하는 타입의 값에 대한 수정이 불필요.

- 메서드 리시버 : 메서드를 통해 호출하는 타입의 값 수정이 필요.

 

일반적으로는 한가지 리시버 형태로 통일하도록 한다. (interface참고)