悩まされたポインタの話

- golang

ポインタとは

ポインタとは - 意味をわかりやすく - IT用語辞典 e-Wordsでは次のように説明されています。

ポインタとは、何かの位置を指し示すための仕組みや道具などのこと。プログラミングでは、変数や関数などが置かれたメインメモリ上の番地などを格納する特殊な変数のことをポインタという。

とのこと。pythonを使う上で使ったことがなかったのでgoのチュートリアルでは頭を悩まされることになりました。

goにおけるポインタ

goではポインタを扱うことができます。 そして、ポインタを使うことでポインタの指す変数を直接変更することができます。

package main

import "fmt"

func main() {
    i := 5
    p := &i         //変数pに変数iのポインタを渡している
    fmt.Println(i)  // 5
    fmt.Println(p)  // 0xc000018030
    fmt.Println(*p) // 5

    *p = 10        //ポインタを操作
    fmt.Println(i) // 10
}

これの何がうれしいかを説明するにはgoについての仕様を知る必要があります。 goにはクラスという概念が存在せず、メソッドという概念のみが存在します。 このため、構造体のフィールドを直接操作できるポインタを使う必要があるというわけです。

メソッドとポインタレシーバ

goでは特定の型に対してメソッドを定義することができます。 そして、メソッドはレシーバ引数という特別な引数を取ります。 以下の例ではAbsメソッドはvという名前のVertex型のレシーバを持ちます。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 { // メソッドの宣言
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs()) // 5
}

そして、goではこのレシーバにポインタ型変数(構造体)を受け取ることができます。 このときのレシーバをポインタレシーバと言います。(そうでないものは変数レシーバと呼ぶ) 最大の違いは変数レシーバは値渡し(つまりコピー)でポインタレシーバは参照渡しということです。 これにより、ポインタレシーバを採用した際にのみ、メソッドからレシーバの値を直接変更することができます。

package main

import "fmt"

type Time struct {
    time string
}

func (s *Time) Afternoon() {
    s.time = "afternoon"
}

func (s Time) Night() {
    s.time = "night"
}

func main() {
    t := Time{"morning"}
    fmt.Println(t.time) // morning
    t.Afternoon()
    fmt.Println(t.time) // afternoon
    t.Night()
    fmt.Println(t.time) // afternoon
}

以上の例ではAfternoonメソッドがポインタレシーバを持っており、Nightメソッドが変数レシーバを持っています。 そのため、変数tはAfternoonメソッドでのみ変更されます。

まとめ

  • メソッド内で変数を書き換えたい場合はポインタをうまく活用しよう!

以上。