ポインタ変数とは
メモリのアドレスを値として持つ変数のこと。
var intVal int // int型の変数
var intPointer *int // intのポインタ変数
intVal = 5
intPointer = &intVal // intPointerはintValのアドレスを指し示す
fmt.Println(&intVal) // 0x0000011
fmt.Println(intPointer) // 0x0000011
fmt.Println(&intPointer) // 0x0000145
メモリは1Byte単位でアドレスがつけられて, そのアドレスで区別される。
上のコードの場合, intVal
はメモリを確保して5
という値を直接格納する。
(下の図は64-bit環境基準でintは8bytesのため, アドレス8個分になっている)
intPointer
はメモリを確保してintValのアドレス
を値として格納する。

ポインタの扱い
ポインタ変数の定義
指し示す変数の型に合わせて*
をつけて定義
var intPointer *int
var boolPointer *bool
var stringPointer *string
ポインタに参照先を設定する
nil
状態のポインタの参照先に値をセットする場合, panicになる
(指してるところがないので, どこに値を入れればいいかわからない状態)
// int型のポインタ変数
var intPointer *int
*intPointer = 5 // panic: runtime error: invalid memory address or nil pointer dereference
変数のアドレスを代入する
一般的には変数のアドレスを代入して使う
var intPointer *int
intVal := 5
intPointer = &intVal
fmt.Println(intPointer) // 0x0000011
fmt.Println(&intVal) // 0x0000011
newでポインタに値をセット
変数を定義せずnew
を利用して無名変数を代入することができる。
var intPointer *int
intPointer = new(int) // ゼロ値のint型がメモリに確保され, それを指し示すようになる
*intPointer = 5 // 指しているメモリアドレスに5をセットする
fmt.Println(*intPointer) // 5
fmt.Println(intPointer) // 0xc000014080
参照先の値を変更
ポインタ変数名に*
をつけると指し示す変数の扱いになる。
intVal := 5
intPointer := &intVal
fmt.Println(intVal) // 5
fmt.Println(*intPointer) // 5
*intPointer = 10 // intVal = 10 するのと同じ
fmt.Println(intVal) // 10
fmt.Println(*intPointer) // 10
ポインタ変数のサイズ
ポインタはメモリアドレスであるため, ポインタのサイズは指し示すデータのサイズではなく,
アドレスを指定できるメモリの最大量に基づいて決まる。
32-bitシステムでは4bytes(32bits), 64-bitシステムでは 8bytes(64bits)になる。
よって, 下記のように指し示すデータ型は異なってもポインタ変数のサイズは64-bitシステムでは全て8になる
type User struct {
name string
birth time.Time
}
func main() {
var intPointer *int
var userPointer *User
var boolPointer *bool
fmt.Println(unsafe.Sizeof(intPointer)) // 8
fmt.Println(unsafe.Sizeof(userPointer)) // 8
fmt.Println(unsafe.Sizeof(boolPointer)) // 8
}
まとめ
スライスや構造体にはポインタがよく使われるのでGoで開発するにはポインタの理解は必要だ。
ポインタは何が値で何がアドレスか分かりにくくなるときがあるが, *
と&
の書き方と意味をちゃんと理解しておけば, ポインタ演算がないGoでは案外難しくないかもしれない。