🔥 메서드와 포인터 Indirection - 1

387자
5분

이전 두 프로그램을 비교해 보면, 포인터 인자를 가진 함수는 반드시 포인터를 받아야 한다는 것을 알 수 있습니다.

var v Vertex
ScaleFunc(v, 5)  // 컴파일 에러!
ScaleFunc(&v, 5) // OK
 
go

반면에 포인터 리시버를 가진 메서드는 호출할 때 값이나 포인터 중 어느 것이든 리시버로 받을 수 있답니다.

var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK
 
go

v.Scale(5) 문장에서, v는 값이고 포인터가 아님에도 불구하고, 포인터 리시버를 가진 메서드가 자동으로 호출됩니다. 즉, Go는 편의상 v.Scale(5) 문장을 (&v).Scale(5)로 해석하는데, 그 이유는 Scale 메서드가 포인터 리시버를 가지고 있기 때문이에요.

아래 예제 코드를 함께 살펴보면서 더 자세히 알아봅시다!

package main
 
import "fmt"
 
type Vertex struct {
	X, Y float64
}
 
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f  // v의 X 값에 f를 곱함
	v.Y = v.Y * f  // v의 Y 값에 f를 곱함
}
 
func ScaleFunc(v *Vertex, f float64) {
	v.X = v.X * f  // v의 X 값에 f를 곱함
	v.Y = v.Y * f  // v의 Y 값에 f를 곱함
}
 
func main() {
	v := Vertex{3, 4}  // v는 Vertex 구조체 값
	v.Scale(2)         // v.Scale 메서드 호출. v의 X, Y가 2배로 커짐
	ScaleFunc(&v, 10)  // ScaleFunc 함수 호출. v의 X, Y가 10배로 커짐
 
	p := &Vertex{4, 3} // p는 Vertex 구조체를 가리키는 포인터
	p.Scale(3)         // p.Scale 메서드 호출. p가 가리키는 Vertex의 X, Y가 3배로 커짐
	ScaleFunc(p, 8)    // ScaleFunc 함수 호출. p가 가리키는 Vertex의 X, Y가 8배로 커짐
 
	fmt.Println(v, p) // 변경된 v와 p의 값 출력
}
 
go

주석을 보면 알 수 있듯이, Scale 메서드는 리시버 vX, Y 값에 인자 f를 각각 곱해서 v를 변경합니다. 마찬가지로 ScaleFunc 함수도 포인터 v가 가리키는 VertexX, Yf를 곱해서 그 Vertex를 변경하지요.

main 함수를 보면, vVertex 값이고 pVertex를 가리키는 포인터입니다. v.Scale(2)를 호출하면 vX, Y가 2배로 커지고, ScaleFunc(&v, 10)을 호출하면 vX, Y가 10배로 커집니다.

비슷하게 p.Scale(3)을 호출하면 p가 가리키는 VertexX, Y가 3배로 커지고, ScaleFunc(p, 8)을 호출하면 p가 가리키는 VertexX, Y가 8배로 커지게 됩니다.

마지막으로 fmt.Println(v, p)으로 vp의 최종 값을 출력하면, 예상한 대로 X, Y 값들이 모두 변경된 것을 확인할 수 있겠죠?

이렇게 Go 언어에서는 메서드가 값 리시버냐 포인터 리시버냐에 따라, 그리고 함수가 값 인자를 받느냐 포인터 인자를 받느냐에 따라 서로 다르게 동작합니다. 하지만 편의를 위해 어느 정도 자동 변환을 지원하기도 하니, 잘 이해하고 활용한다면 더욱 효율적이고 유연한 코드를 작성할 수 있을 거예요!