変数とアドレス
ポインタを理解するための第一歩として「変数」と「アドレス」があります。変数とは、値を保存しておくためのメモリ領域に名前をつけて管理するものであり、アドレスとは、変数がメモリ領域のどこに格納されているかを示すものです。
コンピュータのメモリは、横一列に並べられた巨大なロッカーをイメージするとわかりやすいと思います~~(って偉い人が)~~。ロッカーには端から端まで一意の番号が振られています。ロッカーを探すとき、例えば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