프로그래밍을 배울 때 가장먼저 변수를, 변수를 배우고 얼마 안있어서는 함수라는 것을 배운다.
변수는 특정 데이터를 저장하는 공간이다.
함수는 특정 처리의 묶음이라고 하였다.
함수는 우리가 이미 Go10강에서 언급하였다.
그리고 경우에 따라서 변수의 묶음인 객체를 필요로 하게 된다. Go11강 에서 struct와 객체를 언급하였다.
프로그래밍 자체가 원래 반복하는 작업을 빠르게 처리하기 위해서 존재하는 것이다.
즉 변수의 묶음인 객체와 처리의 묶음인 함수는 프로그래밍에서 매우 중요하게 언급된다. 이는 어떤 언어라도 마찬가지이다.
그런데 객체는 변수만을 가지고 있다보니 효율적인 코딩이 힘들게 된다.
가령 컴퓨터라는 객체가 있다고 가정해보자.
컴퓨터는 cpu, 램, 그래픽카드, 메인보드 등등으로 이루어져있다.
여기서 cpu, 램, 그래픽카드, 메인보드는 변수라고 할 수 있다.
하지만 컴퓨터의 역활이 이러한 부품을 저장해두는 것인가?
컴퓨터는 게임을하기위해서, 영화를 보기위해서, 작업을 하기위해서, 공부를 하기 위한, 기능들이 필요하기 때문에 컴퓨터가 존재하는 것이다.
Method => 기능
위의 개념을 그대로 프로그래밍에 옮깃것이 바로 메소드다.
메소드는 한국말로 번역하면 기능이라는 뜻인데 일반적으로 메소드를 가장 쉽게 정의 하자면
객체에 연결된 함수
즉 함수이긴 한데 객체에 연결되어야 메소드라고 부른다.
우리가 특정 객체를 통해서 하려고하는 작업들(함수들)을 메소드라고 생각하면 편하다.
아직 메소드에 대해서 배우기 전이므로 약간 뜬구름 잡는다고 생각할 수 있다.
그럼 예제를 통해서 보도록 하자.
package main
import "fmt"
type Point struct {
X,Y int
}
func (p Point) printInfo() {
fmt.Println(p)
}
func main() {
p := Point{3,4}
p.printInfo()
}
여기서 우리는 함수로 printInfo라는 것을 선언했다.
그런데 자세히 보면 함수가 약간 특이하다는걸 발견할 수 있다.
func (p Point) printInfo() {
fmt.Println(p)
}
함수명 앞에 타입과 변수명이 붙어 있다.
이런걸 리시버(Receiver)라고 부른다. 이 리시버는 특별히 밸류 리시버(Value Receiver)라고 부른다.
리시버가 장착된 함수는 더 이상 함수가 아니라 메소드가 된다.
위의 경우 Point p타입의 리시버가 붙었으므로 Point 타입에서 사용하는 메소드가 된다.
결과를 예상했는가?
이 메소드라는 것은 Point의 식구가 된 것이다.
뜬금없이 갑자기 왜 식구라는 이야기를 했냐면 이제부턴 private변수에 마음껏 접근할 수 있다.
그야 식구이기 때문이다.
package main
import "fmt"
type Point struct {
X, Y, z int
}
func (p Point) printInfo() {
fmt.Println(p.X)
fmt.Println(p.Y)
fmt.Println(p.z)
}
func main() {
p := Point{3, 4, 5}
p.printInfo()
}
원래 다른 함수에서 접근하는것은 불가능 했다.
그러나 printInfo라는 함수는 Point 타입의 리시버를 달고 있으므로 Point타입의 메소드이다.
당연히 접근이 가능하다.
잠시 정리
함수(function) - 처리의 묶음
객체(object) - 변수의 묶음
구조체(struct) - 객체를 선언하는 방법
리시버(reciever) - 객체와 함수를 연결하는 매개체
메소드(method) - 객체에 연결된 함수
value receiver - 객체를 value로 가져와서 사용하는 리시버
pointer receiver - 객체를 referene(pointer)로 가져와서 사용하는 리시버
여기서 우리는 value receiver만 언급했다.
그럼 반대편에 있는 pointer receiver도 언급할 차례이다.
call by value, call by reference를 안다면 예측할 수 있다.
그러나 모르는 사람도 있을 수 있으므로 그냥 설명하겠다.
package main
import "fmt"
type Point struct {
X,Y int
}
func (p *Point) add(a int) {
p.X += a
p.Y += a
}
func (p Point) mul(a int) {
p.X *= a
p.Y *= a
}
func main() {
p := Point{3,4}
p.add(10)
p.mul(100)
fmt.Println(p)
}
자 이러한 코드가 있다고 가정해보자.
Point는 두개의 메소드가 선언된 상태이다.
근데 잘보면 메소드의 리시버의 상태가 다르다.
add는 pointer리시버가 붙어있다.
mul은 value리시버가 붙어있다.
결과가 어찌 될지 예상이 되는가?
1. 1300, 1400이 된다.(둘다 적용)
2. 3, 4가 된다.(둘다 적용 안됨)
3. 13, 14가 된다.(add만 적용)
4. 30, 40이 된다.(mul만 적용)
결과가 어찌 될지 예상하고 아래의 답변을 보도록 하자.
정답이 예상이 되는가? 왜 이런 값이 나올까?
func (p *Point) add(a int) {
p.X += a
p.Y += a
}
이 메소드의 리시버는 pointer 리시버다.
여기서의 복제는 값이 복제되는게 아니라 레퍼런스가 넘어간다.
즉 여기서의 p는 원본 그 자체이다. 포인터로 받았으니까.
func (p Point) mul(a int) {
p.X *= a
p.Y *= a
}
반면 이 메소드의 리시버는 value 리시버이다.
여기서의 복제는 값이 복제된다. 즉 다른 값이다.
이는 아래의 코드에서 확인할 수 있다.
만약 값을 안에서 바꾸고 싶다면 포인터 리시버를 사용하고 그게 아니라면 value 리시버를 사용하는게 좋다.
'Programming > Go' 카테고리의 다른 글
[Go-17]에러(Error)처리 (0) | 2019.02.26 |
---|---|
[Go-16]메소드(Method)와 인터페이스(Interface) (1) | 2019.02.25 |
[Go-14]함수와 클로저, 그리고 고차함수와 익명함수(람다함수) (0) | 2019.02.18 |
[Go-13]range와 범위 (0) | 2019.02.17 |
[Go-12]struct와 포인터, 그리고 초기화 (0) | 2018.09.03 |