Go 言語のポインター
Go のアドレス演算子
Go では変数に & を付けることで、その変数のメモリ上のアドレスを取得できます。 アドレスというのはその名の通り、メモリ内での場所 (住所) のことです。
次の例では、3行目で変数 x のアドレスを表示しています。
var x int32 = 10
fmt.Println(" x = ", x) // x = 10
fmt.Println("&x = ", &x) // &x = 0xc000118000
実行時の表示は一例です。環境によって異なります。
変数のアドレスを取り出す & はアドレス演算子 (address operator) といいます。
Go のポインター
Go ではアドレスを格納するための変数が用意されています。これを ポインターといいます。
ポインターはデータ型毎に、 int32 型のポインターの型は *int32、 byte 型のポインター型は *byte という具合に、 型名に * を付ける形で宣言できます。
次の例では、int32 型の変数 x のアドレス (&x) を、*int32 型のポインター変数 p に代入しています (6行目)。
var x int32 = 10
fmt.Println(" x = ", x) // x = 10
fmt.Println("&x = ", &x) // &x = 0xc000118000
var p *int32
p = &x
fmt.Println(" p = ", p) // p = 0xc000118000
ポインターの初期値は nil です。nil はどのアドレス情報も保持していない状態です。
次の例では、ポインターを宣言して、直ちにその内容をプリントしています。
var p *int32
fmt.Println(" p = ", p) // p = <nil>
nil を fmt.Println で表示すると <nil> と表示されます。
Go のポインターのデリファレンス
Go のポインター変数に * を付けると、そのポインター変数が保持しているアドレスに書き込まれている値を意味します。
この場合の * を間接演算子 (indirection operator) といいます。そして、間接演算子を使ってポインターが保持するアドレスを参照することをデリファレンス (dereferencing) といいます。
次の例では6行目で変数 x のアドレスを、ポインター変数 p に代入。そして、 8行目でポインター p が指し示す内容 (つまり変数 x の値) を書き換えています。9行目では x の値が書き換えられていることが確認できます。
var x int32 = 10
fmt.Println(" x = ", x) // x = 10
fmt.Println("&x = ", &x) // &x = 0xc00001407c
var p *int32
p = &x
fmt.Println(" p = ", p) // p = 0xc00001407c
*p = 20
fmt.Println(" x = ", x) // x = 20
Go のポインターの算術演算は許可されていない
Go のポインターのシンタックスは C 言語に由来していますので、 C 言語に慣れている方には馴染みのあるものだと思います。
特定のバッファのポインターを取得したら、データサイズ分ポインターを足したりひいたりしてバッファの内容を参照する、ということはよくやる操作ですね。
しかし、Go では C/C++ 言語と違ってそうしたポインターの算術演算は許可されていません。
Go のポインターのまとめ
以上、Go のポインターについて説明しました。
いろんな名前が出てきて、ウッとひるんだ方もいるかもしれませんので、最後に簡単に内容をまとめておきます。
- 普通の変数のアドレスは & で取り出せる (& は「アドレス演算子」)
- ポインター変数はアドレスを保持する
- ポインター変数が保持するアドレスに書き込まれた値は * を付けて参照できる (* は「間接演算子」。間接演算子でアドレスの参照する値を参照することは「デリファレンス」)
- Go のポインターは算術演算はない
このポイントを抑えておけば、基本的なコードを書くのに十分です。