変数とアドレス
ポインタを理解するための第一歩として「変数」と「アドレス」があります。変数とは、値を保存しておくためのメモリ領域に名前をつけて管理するものであり、アドレスとは、変数がメモリ領域のどこに格納されているかを示すものです。
コンピュータのメモリは、横一列に並べられた巨大なロッカーをイメージするとわかりやすいと思います(って偉い人が)。ロッカーには端から端まで一意の番号が振られています。ロッカーを探すとき、例えば8桁の番号で表されても面倒なのでロッカーには名前を書いておこうというわけです(実際には16進数で表されます)。
package main
import "fmt"
func main() {
var hoge string
hoge = "Hello"
fmt.Println("hogeの値:", hoge)
fmt.Println("hogeのアドレス:",&hoge)
}
hogeの値: Hello
hogeのアドレス: 0xc0000101e0
実行結果を見てみると、Hello
という文字列が0xc0000101e0
番地のメモリ領域に変数名hoge
として格納されていることがわかります。注意点として、変数が格納されるアドレスは実行環境によって異なります。再実行してみると変数が別のアドレスに格納されることがわかると思います(変わらないこともあります)。
このように変数とアドレスは切っても切れない関係にあります。
ポインタ
ポインタとは、変数のアドレスを示すものです。変数のアドレスを示す変数を「ポインタ変数」、その型を「ポインタ型」といいます。
package main
import "fmt"
func main() {
var hoge string
hoge = "Hello"
var p *string
p = &hoge
fmt.Println("hogeの値:", hoge)
fmt.Println("hogeのアドレス:",p)
fmt.Println("pが指すアドレスの値:",*p)
}
hogeの値: Hello
hogeのアドレス: 0xc000092030
pが指すアドレスの値: Hello
ポインタ型を定義するには既存の型に*
を付けます。ここではstring
型の変数のアドレスを保持したいので、*string
と書きます。また、変数のアドレスを取得したい場合には、変数にアドレス演算子&
を用います。ここで取得したいのはhoge
のアドレスなので、&hoge
と書きます。つまり、ポインタ変数p
はhoge
のアドレスを保持していることになります。
逆にポインタ変数からアドレス内の値を知りたい場合には、ポインタ変数に間接参照演算子*
を用います。実行結果は、p
が指すアドレス0xc000092030
にはHello
が格納されていることを示しています。
ちなみに、ポインタ変数のゼロ値はnil
です。そりゃそうだ。
var p *string
fmt.Println(p)
<nil>
変数とポインタ変数
変数とポインタ変数の使い分けについて考えたいと思います。
package main
import "fmt"
func main() {
var hoge string
hoge = "Hello"
var fuga string
fuga = hoge
var piyo *string
piyo = &hoge
fmt.Println("hogeの値:", hoge)
fmt.Println("fugaの値:", fuga)
fmt.Println("piyoが指すアドレスの値:", *piyo)
fuga = "World"
fmt.Println("fuga更新後のhoge:", hoge)
*piyo = "World"
fmt.Println("piyoが指すアドレスの値更新後のhoge:", hoge)
fmt.Println("hogeのアドレス:", &hoge)
fmt.Println("fugaのアドレス:", &fuga)
fmt.Println("piyoが指すアドレス", piyo)
}
hogeの値: Hello
fugaの値: Hello
piyoが指すアドレスの値: Hello
fuga更新後のhoge: Hello
piyoが指すアドレスの値更新後のhoge: World
hogeのアドレス: 0xc000088030
fugaのアドレス: 0xc000088040
piyoが指すアドレス 0xc000088030
fuga
には単なる変数hoge
を代入しています。2つの変数は同じ値を保持していますが、それぞれの変数が格納されたアドレスを確認してみるとその値は異なっています。これはメモリ上の別のアドレスに同じ値を持った2つの変数が存在していることを示しています。当たり前ですが、2つは別の変数ですのでfuga
の値を変更したとしても、それがhoge
へ反映されることはありません。
piyo
にはhoge
のアドレスを代入しています。hoge
が格納されているアドレスとpiyo
が指すアドレスは同じなので、間接参照演算子を用いて*piyo
の値を変更すると、それはhoge
へも反映されます。
このような機能から、Goにおけるポインタは、構造体のメソッドで何らかの処理を行う場合にポインタレシーバを用いるケースが多く見られます。引数やレシーバで何らかの値を受け取り、その処理を呼び出し元に反映させるにはポインタを用いる必要があるということです。
参考リンク
https://golang.org/ref/spec#Pointer_types
https://tour.golang.org/moretypes/1
https://9cguide.appspot.com/05-01.html
https://9cguide.appspot.com/15-01.html