Go のポインタとは - アドレスと間接参照の基本
プログラミングにおいて、変数はメモリ上のどこかに格納されています。ポインタとは、その「どこか」を指し示す値のことです。Go ではポインタを明示的に扱う構文が用意されており、データの共有や効率的な受け渡しに欠かせない仕組みとなっています。
メモリアドレスという概念
変数を宣言すると、Go のランタイムはメモリ上に領域を確保し、そこに値を格納します。このとき、確保された領域には固有の番地(アドレス)が割り振られています。
package main
import "fmt"
func main() {
x := 42
fmt.Println("値:", x)
fmt.Println("アドレス:", &x)
}& は「アドレス演算子」と呼ばれ、変数の前に付けるとその変数のメモリアドレスを取得できます。実行すると、値として 42 が、アドレスとして 0xc0000b6010 のような 16 進数の番地が出力されます。このアドレスは実行のたびに変わりますが、プログラムの実行中は同じ変数に対して同じアドレスが維持されます。
ポインタの正体
ポインタとは、別の変数のアドレスを格納するための変数です。通常の変数が「値そのもの」を保持するのに対し、ポインタは「値が置かれている場所」を保持します。
値そのものを格納する。x := 42 なら、x のメモリ領域に 42 が直接入っている。
値のアドレスを格納する。p := &x なら、p のメモリ領域に x のアドレスが入っている。
ポインタ型は *T という形式で表現されます。T は指し示す先の型です。たとえば *int は「int 型の値を指すポインタ」を意味します。
package main
import "fmt"
func main() {
x := 42
var p *int = &x
fmt.Println("p の値(アドレス):", p)
fmt.Println("p が指す先の値:", *p)
}ここで登場する *p は「デリファレンス(間接参照)」と呼ばれる操作です。ポインタが指し示す先のメモリにアクセスし、そこに格納されている値を取り出します。
& と * の関係
& と * は対になる演算子です。& が変数からアドレスを取り出し、* がアドレスから値を取り出します。
変数 x に値 42 が格納されている
&x で x のアドレスを取得する
そのアドレスをポインタ変数 p に格納する
*p で p が指す先の値 42 を取得する
この双方向の変換がポインタ操作の核心です。& でアドレスの世界へ行き、* で値の世界へ戻ってくるというイメージで捉えるとわかりやすいかもしれません。
ポインタ経由で値を書き換える
ポインタのデリファレンスは読み取り専用ではありません。*p に対して代入を行うと、ポインタが指す先の値を直接変更できます。
package main
import "fmt"
func main() {
x := 42
p := &x
fmt.Println("変更前:", x)
*p = 100
fmt.Println("変更後:", x)
}*p = 100 を実行すると、p が指している x のメモリ領域に 100 が書き込まれます。結果として x の値も 100 に変わります。p と x は同じメモリ領域を参照しているため、片方を通じた変更がもう片方にも反映されるわけです。
ポインタが必要になる場面
Go の関数は引数を「値渡し」で受け取ります。つまり、関数に変数を渡すと、その変数のコピーが作られ、関数内部ではコピーに対して操作が行われます。
package main
import "fmt"
func tryChange(n int) {
n = 999
}
func main() {
x := 42
tryChange(x)
fmt.Println(x) // 42 のまま
}tryChange の中で n を変更しても、main の x には影響がありません。コピーが変更されるだけだからです。関数の外にある変数を変更したい場合は、ポインタを渡す必要があります。
package main
import "fmt"
func changeValue(p *int) {
*p = 999
}
func main() {
x := 42
changeValue(&x)
fmt.Println(x) // 999
}&x でアドレスを渡し、関数側で *p を通じて元の変数を操作しています。この仕組みにより、関数をまたいだデータの共有が可能になります。ポインタは Go のプログラムにおいて、値のコピーを避けたいときや、呼び出し元の状態を変更したいときに登場する基本的な道具です。