Golangのポインタとは?
値型データのメモリ上にあるアドレスと型の情報のことです。
もう少し噛み砕くと、ある変数が保存されているアドレス情報とその変数の型情報をポインタと言います。
ポインタの基本的な使い方は以下の記事で詳しく解説しています。
Golangのポインタの構文
Golangのポインタの構文は実にシンプルで、以下のように書きます。
var sampleP *データ型
var sampleP *int // int型のポインタを定義
ちなみに、ポインタのゼロ値はnilとなります。
Golang ポインタの初期化
Golangのポインタの初期化方法は、変数の前に(&)演算子を付けることで初期化できます。
package main
import (
"fmt"
)
func main() {
var num int = 98
var intPointer *int // ポインタを定義
intPointer = &num // ポインタを初期化
fmt.Println(intPointer) // 0xc0000b2008
}
Golang ポインタのデリファレンス
ポインタの逆参照とは、ポインタが保持するアドレスの中の値を取得することです。これをデリファレンスと言います。
メモリアドレスがあれば、そのメモリアドレスへのポインタをデリファレンスして、その中にある値を得ることができます。
package main
import (
"fmt"
)
func main() {
var num int = 98
var pointer *int
pointer = &num
fmt.Println(pointer) // 0xc000016090
fmt.Println(*pointer) // 98
}
ポインタ型のポインタ
ポインタも変数なので、実はポインタのアドレスも格納することができます。つまり、ポインタ型の変数のポインタを作ることができるということです。
package main
import (
"fmt"
)
func main() {
a := 98
b := &a // bはint型のポインタ
c := &b // cはbのポインタ(つまり、ポインタ型のポインタ)
fmt.Println(a) // 98
fmt.Println(b) // 0xc0000b2008
fmt.Println(*b) // 98 (アドレスの中の値)
fmt.Println(c) // 0xc0000ac018
fmt.Println(*c) // 0xc0000b2008 (bのアドレス)
}
このような間接参照は、不必要な混乱を引き起こすことがあるので、使用する際には注意が必要です。
interfaceのポインタ
ポインタは、インターフェイス型の値でも使用することができます。
また、空のインターフェースがポインタになった場合、 nilが返されます。
package main
import (
"fmt"
)
func main() {
var x interface{}
y := &x
fmt.Println(y) // 0xc000096210
fmt.Println(*y) // <nil>
}
以下はインターフェースのポインターを使用したコード例です。
package main
import (
"fmt"
)
// インターフェースを定義
type Bird interface {
fly()
}
type B struct {
name string
}
// インターフェースを実装
func (b B) fly() {
fmt.Println("飛びます")
}
func main() {
var bird Bird = B{"鷲"}
birdPointer := &bird
fmt.Println(birdPointer) // 0xc000096210
fmt.Println(*birdPointer) // {鷲}
}
ここで、変数birdはBird型の構造体で、インターフェース型として使われています。
これがポリモーフィズムの挙動となります。
このように、構造体やインタフェースへのポインタは、Golangでは必須のツールです。
関数の引数としてのポインタ
ポインターは関数の引数でも使用することができます。
また、値を引数に渡すよりもアドレス(ポインタ)を渡した方が、大きなオブジェクトの値を扱う際は効率的な処理を行えます。
package main
import (
"fmt"
)
// ポインタ型の引数を定義
func sample(num *int) {
fmt.Println(*num)
}
func main() {
var num int = 98
// アドレスを引数に渡す
sample(&num) // 98
}
大きなオブジェクトを使うと、実行時間が遅くなることがありますが、構造体へのポインタを渡すと効率的に扱うことができます。
package main
import (
"fmt"
)
type Person struct {
name string
age int
}
func sample(p *Person) {
fmt.Println((*p).name, "は", (*p).age, "歳です。")
}
func main() {
john := Person{"田中", 20}
sample(&john) // 田中 は 20 歳です。
}
構造体をデリファレンスする場合は、注意が必要です。
もし、[*構造体名.フィールド名] のような使い方をすると、エラーを出します。正しくは、[(*構造体名).フィールド名] です。
関数内部でポインタを使用すると、その値がconstでない限り、変更可能な値になります。
なので、ある値を変更したいときはいつでも、その値へのポインタを関数の引数として使用し、必要な変更を行う必要があります。
Golangの “new”関数
Golangのnew関数は、引数に指定したデータ型のポインタを返します。
package main
import (
"fmt"
)
func main() {
test := new(int)
*test = 98
fmt.Println(test) // 0xc000096210
fmt.Println(*test) // 67
}
関数からポインタを返す
どのような型のポインタでも、他の値と同じように関数から戻り値を返すことができます。
やり方としては、値を直接返すのではなく、単にその値のアドレスを返すだけです。
package main
import (
"fmt"
)
func sample() *int {
v := 98
return &v // アドレスを返す
}
func main() {
n := sample()
fmt.Println(n) // 0xc000016090
fmt.Println(*n) // 98
}
関数へのポインタ
Golangにおいて関数へのポインタは、暗黙的に働きます。
つまり、ポインタとして宣言する必要はありません。
package main
import (
"fmt"
)
func main() {
sample := func() {
fmt.Println("関数です")
}
x := sample
x() // 関数です
}
Golangでポインターを使用する際の注意点
Goはポインタの演算を許しません。
なので、C/C++でできるような単項のインクリメントやデクリメントのようなことはできないので注意しましょう。