🚈
【Go】値レシーバとポインタレシーバについて
はじめに
goの学習を始めて躓いた部分について調査したので、今のところの自分の理解をまとめる。
ポインタの復習
- メモリアドレスが格納されている変数
- この住所を参照することによって変数の値を変更する(jsなどの参照渡しの概念に似てる気がする)
- 変数からポインタを受け取るには"&"を変数の前につける
func main() {
p := 1
fmt.Println(p, &p)
}
// 1 0xc00000a158
- 変数への代入は単純な値コピーとなり、異なるメモリ領域にコピーが作られる
- 代入後に元の値を変更してもコピー先には反映されない
func main() {
// 値型の例
p := 1
q := p
q = 10
fmt.Printf("pの値: %v\n", p)
fmt.Printf("pのアドレス: %v\n", &p)
fmt.Printf("qの値: %v\n", q)
fmt.Printf("qのアドレス: %v\n", &q)
}
// pの値: 1
// pのアドレス: 0xc00000a158
// qの値: 10
// qのアドレス: 0xc00000a180
- 変数には値型とポインタ型がある
- 値型:
- 変数が直接データを保持する
- 変数に代入した場合は値をコピー
- ポインタ型
- 値のメモリアドレスを保持する
- 変数に代入したらアドレスをコピー
- 値型:
func main() {
// 値型の例
i := 10
j := i // iの値がjにコピーされる
j = 20
fmt.Println(i) // 10: iの値は変わらない
// ポインタ型の例
i = 10
p := &i // pはiのアドレスを保持
*p = 20 // pが指す値(つまりi)を変更
fmt.Println(i) // 20: iの値が変わる
}
メソッドとは
特定の型に紐づけられた変数。
その型の変数に対して操作を行うことができる。
func (レシーバ レシーバの型) メソッド名(引数 引数の型) 戻り値の型 {
// 処理
}
この例では型Userに対してメソッドを定義し、User型の変数から呼んでいる。
goにはクラスがないが、クラスと同じ感覚で理解できる
// 自己紹介するメソッド
func (h User) introduce() {
fmt.Printf("I am %v years old\n", h.age)
fmt.Printf("h.ageのアドレス: %v\n", &(h.age))
}
func main() {
u := User{
ID: 1,
Name: "Taro",
age: 20,
}
u.introduce()
}
// I am 20 years old
レシーバについて
- 値型に対してメソッドを定義するか、ポインタ型に対してメソッドを定義するのかの2種類がある。
値型レシーバ
値型のレシーバにした場合、メソッドが呼び出される度に新しい構造体にコピーされる。
そのため、値レシーバのメソッド内でフィールドを変更した後に参照しても変更が反映されない。
// 自己紹介するメソッド
func (h User) introduce() {
fmt.Printf("I am %v years old\n", h.age)
fmt.Printf("h.ageのアドレス: %v\n", &(h.age))
}
// 年齢を1つ増やすメソッド
func (h User) setAge(age int) { //値レシーバ
h.age = age
}
func main() {
u := User{
ID: 1,
Name: "Taro",
age: 20,
}
u.introduce()
u.setAge(30)
u.introduce()
}
// アドレスが異なるので、変更が反映されない
// I am 20 years old
// h.ageのアドレス: 0xc000064498
// I am 20 years old
// h.ageのアドレス: 0xc0000644d8
ポインタ型レシーバ
レシーバをポインタにすることで、メソッドからレシーバの値を直接変更できる
// 自己紹介するメソッド
func (h User) introduce() {
fmt.Printf("I am %v years old\n", h.age)
fmt.Printf("h.ageのアドレス: %v\n", &(h.age))
}
// 年齢を1つ増やすメソッド
func (h *User) setAge(age int) { //ポインタレシーバ
h.age = age
}
func main() {
u := User{
ID: 1,
Name: "Taro",
age: 20,
}
u.introduce()
u.setAge(30)
u.introduce()
}
// アドレスが同じなので変更が反映される
// I am 20 years old
// h.ageのアドレス: 0xc000064498
// I am 30 years old
// h.ageのアドレス: 0xc000064498
まとめ
値レシーバ:
- メソッドが呼び出されるたびに新しいコピーが作成される
- メソッド内で変更しても元のフィールドには反映されない
ポインタレシーバ
- ポインタを受け取るので、メソッド内の変更が反映される。
- メモリ上のアドレスを渡すだけなので、メモリを食わない
基本的には値を変更したくてもいいときは値レシーバ、それ以外ではポインタレシーバって覚えればいいかな。
Discussion