エンジニアに転職して初めてGoを使うことになってのですが、アドレスとポインタという概念がよくわからなかったので簡単にまとめてみました!
アドレスとポインタとは?
- アドレス:変数が存在するメモリ上の場所を「アドレス」
- ポインタ:アドレスを格納可能な変数
アドレスについて
構造体 Capital
をもとに「ID: 1、Name: 東京」という具体的な値を持つ japan
という変数を作成するコードを見てみます。
package main
import "fmt"
type Capital struct {
ID int
Name string
}
func main() {
japan := Capital{ID: 1, Name: "東京"}
// この時japanという変数と、変数の場所(アドレス)が作成される
fmt.Println("japanの中身:", japan)
// 出力結果 -> japanの中身: {1 東京}
}
このとき、Goでは「japan に格納されたデータがメモリのどこに配置されたか(アドレス)」という情報も自動的に管理されます。
このメモリ上の場所のことを アドレス と呼びます。
ちなみにこのアドレスは、プログラムの実行中にRAM上に一時的に割り当てられているものです。
そのため、アプリを閉じてもう一度開くと、通常は違うアドレスに配置されるようになります。
ポインタについて
先ほどの例で、ひとつ覚えておきたいポイントがあります。
japan := Capital{ID: 1, Name: "東京"}
のように値として構造体を代入した場合、japan には「構造体の中身(値そのもの)」が入っているだけで、ポインタのように「自分がどこにあるか(アドレス)」を直接保持しているわけではありません。
つまり、japan は「ID: 1, Name: 東京」というデータをそのまま持っていますが、「この構造体がメモリのどこにあるのか?」というアドレス情報は、Goが内部で自動的に管理しています。
アドレスを取得したい場合は、&japan のようにアドレス演算子を使って明示的に取り出す必要があります。
package main
import "fmt"
type Capital struct {
ID int
Name string
}
func main() {
japan := &Capital{ID: 1, Name: "東京"}
// 「&」を使って構造体のアドレスを japan に格納(=ポインタを作成)
fmt.Println("japan(ポインタ):", japan)
// 出力結果 -> japan(ポインタ): 0xc000014060
}
このように、&Capital{...} のように & を付けて初期化することで、japan には「ID: 1, Name: 東京」といった値そのものではなく、その値が格納されている場所(アドレス)が入るようになります。
このときの japan は、値ではなくアドレスを持っているので、「ポインタ」 と呼ばれる変数になります。
終わりに
なんとなく理屈はわかったけど、このポインタとアドレスって結局どうやって使われるの?
ポインタを使う時
- APIで受け取ったデータを関数で更新するとき(例:DBに保存する前に整形など)
- 大きな構造体を何度も関数に渡す(メモリ節約)
ポインタを使わない時
- ログ出力や画面表示だけのためにデータを渡すとき
- 「元の値は壊したくない」けど仮に値を変えてテストや一時的な操作をしたいとき
おまけ
そういえば、「ID: 1、Name: 東京」という構造体の中の個々の値にもアドレスがありますが、それはどうやって確認すればいいのでしょうか?
package main
import "fmt"
type Capital struct {
ID int
Name string
}
func main() {
japan := Capital{ID: 1, Name: "東京"}
fmt.Println("japan.ID のアドレス:", &japan.ID)
// 出力結果 -> japan.ID のアドレス: 0xc000014060
fmt.Println("japan.Name のアドレス:", &japan.Name)
// 出力結果 -> japan.Name のアドレス: 0xc000010230
}
このように値の前に&をつけることで確認できます。
あれ、これでもいいんじゃない?って思いませんか?
func main() {
japan := &Capital{ID: 1, Name: "東京"}
fmt.Println("japan.ID のアドレス:", japan.ID)
// 出力結果 -> japan.ID のアドレス: 1
fmt.Println("japan.Name のアドレス:", japan.Name)
// 出力結果 -> japan.Name のアドレス: 東京
}
でもこれだとアドレスではなくて値が出力されます。
なぜ?
これはGoの仕様らしいです。
ポインタからでも japan.ID
のようにドットでアクセスすると、自動的に*japan.ID
をしたことになって値を取り出してくれるらしいです。
*
は「ポインタが指している先の中身を取り出す」という意味があります。(この操作を「デリファレンス」っていいます。)
例えば以下のコードのように書くと、ポインタのアドレスではなく値を取り出すことができます。
japan := &Capital{ID: 1, Name: "東京"}
fmt.Println("*japan の中身:", *japan)
// 出力結果 -> *japan の中身: {1 東京}
fmt.Println("*japan の中身:", japan)
// 出力結果 -> *japan の中身: 0xc000014060
実は、&
と*
は逆の役割があります。
-
&
は「アドレスを取得する」 -
*
は「アドレスの先の中身(値)を取り出す」
つまり、
どこにあるのか知りたい → &
中身が欲しい → *
このシンプルなイメージで使い分けることができます。