これは、「Happiness Chain Advent Calendar 2023」の 17 日目の記事です。
はじめに
現在、Happiness Chain で Go の学習をしています。
その際、Go の「ポインタ」が自分としてはあまり馴染みがなく、使っていてもいまいちしっくりとこなかったり、使いどころがわからなかったりしているので、知識の整理をしていきたいと思います!
ポインタとは
ポインタとは、値型に分類されるデータ構造のメモリ上の「アドレス」と「型」の情報のことです。
たとえば、a
という変数宣言をした際に、その変数用にメモリの領域を確保します。
このとき、その確保した場所を示すのが「アドレス」です。
「0xc0000120d0
」というような値として表示されます。
package main
import "fmt"
func main() {
p := 3
ip := &p
fmt.Printf("type=%T, address=%p", ip, ip)
}
// ipはintのポインタ型で、その値が「0xc0000120d0」となる
type=*int, address=0xc0000120d0
ポインタの定義と値の参照方法
ポインタの定義
ポインタは、「int
」や「float
」などポインタを使って参照・操作したい型の前に「*
」を置くことで定義します。
var ip *int // int型のポインタ
var fp *float64 // float64型のポインタ
任意の型からポインタ型を作成
アドレス演算子「&
」を使うことで、任意の型からポインタ型を作成することができます。
var i int // int型の変数
ip := &i // int型のポインタを作成
fmt.Printf("%T\n", ip)
*int
ポインタ型の変数から値を参照する方法
ポインタ型が保持するメモリ上のアドレスを使用してデータ本体を参照する仕組みのことをデリファレンスといいます。
デリファレンスをするには、ポインタ型の前に「*
」を配置します。
package main
import "fmt"
func main() {
var i int // int型の変数
ip := &i // int型のポインタを作成
i = 3
fmt.Printf("ip = %v\n", ip)
fmt.Printf("*ip = %v\n", *ip)
}
ポインタを操作して値を書き換える場合も、デリファレンスを用います。
ip = 0xc000112008 // ipの値(アドレス)
*ip = 3 // デリファレンスで変数iの値を参照
package main
import "fmt"
func main() {
var i int // int型の変数
ip := &i // int型のポインタを作成
i = 3
fmt.Printf("i = %v\n", i)
fmt.Printf("ip = %v\n", ip)
fmt.Printf("*ip = %v\n", *ip)
*ip = 100
fmt.Printf("i = %v\n", i)
fmt.Printf("ip = %v\n", ip)
fmt.Printf("*ip = %v\n", *ip)
}
i = 3
ip = 0xc0000120d0
*ip = 3
i = 100 // 変数iを示すポインタ変数ipの値を参照し、書き換えたため、iの値も変わる
ip = 0xc0000120d0
*ip = 100
ポインタの使いどころ
上記の通り、ポインタの基本的な概念と、その使い方は整理できました。
次は、ポインタを使うときと使わないところの違いがよくわからないため、「ポインタ」を使う主な場面をまとめたいと思います。
- 引数やレシーバ(Go で構造体のフィールドやその他のデータ型にアクセスする方法を提供するメソッドの引数)の値を関数内で書き換えたいとき
- 引数やレシーバにポインタを使わな場合、その関数内で値を書き換えても、引数やレシーバの値は変わることはありません
- これは、引数やレシーバに当てはめたときにはコピーを作成することになり、コピーを変更することになるためです
- 書き換えたい場合は、ポインタを使う必要があります
- コピーを避けたいデータを引数、レシーバにする場合
- 構造体にはコピーが発生すると問題が生じるものがあり、そのような構造体はポインタで扱うことで、コピーをされないようにします
- コピーのコストが大きくなるような、大きな構造体や配列を扱う場合や大きな構造体をスライスに持たせる場合
- 大きな構造体や配列は、コピーのコストが大きいため、ポインタで扱うのが良いです
おわりに
以上、ポインタについてまとめさせていただきました。
この場合はポインタを使った方が良い、というとことがまとめる前よりも理解できたような気がするので、これからさらに学習を進める上で、うまく利用をしてきたいと思います!