ポインタ
とは整数値をアドレスとして扱う型です
アドレスはポインタにキャストされた整数値です
ではこのアドレスで何を指すかといえば、それはメモリ内におけるデータの格納場所です
単純な例から考えます
func main() {
var x int
var y *int
y=&x
*y=2
fmt.Println(x)
}
var
式は変数宣言です
ここではint
型の変数及びポインタを生成しています
ポインタy
は変数x
のアドレスを取得しています
一度アドレスを保持すると、以降はy
からx
の領域へアクセスできるようになります
アクセス時の宣言が*y
です
アクセスした領域に対しては代入も可能です
この操作は参照外し
と呼ばれます
これを踏まえて、例のコードを確認します
package main
import "fmt"
// Vertex構造体の定義
type Vertex struct {
x, y int
}
// 値レシーバのAreaメソッド
func (v Vertex) Area() int {
return v.x * v.y
}
// ポインタレシーバのScaleメソッド
func (v *Vertex) Scale(i int) {
v.x = v.x * i
v.y = v.y * i
}
// New関数Vertexのポインタを返す
func New(x, y int) *Vertex {
return &Vertex{x, y}
}
func main() {
v := New(3, 4) //Vertexインスタンスを作成
v.Scale(10)
fmt.Println(v.Area())
}
レシーバ
とはメンバを呼び出すインスタンスのことです
ここではVertex
構造体のインスタンスがそれに当たります
*Vertex
はこのインスタンスのポインタです
New
関数が戻り値にそのポインタを返しています
冒頭でも触れたように、ポインタの正体はアドレスとして振る舞う整数なので、通常の整数と同じく戻り値として扱えます
ここでは&Vertex{x,y}
としてインスタンス化直後のVertex
のアドレスを直接返しています
関数を抜けるので一見このインスタンスは消滅しそうに思われますが、Go
の仕様によってアドレスを返すインスタンスは動的にヒープへ確保されるため、その心配はありません
例えば以下のコードは有効です
func main() {
var x *int=test()
fmt.Println(*x)
}
func test () *int{
x:=100
return &x
}
Area
とScale
の二つの関数はレシーバとして指定されるデータ型がそれぞれ異なっています
// 値レシーバのAreaメソッド
func (v Vertex) Area() int {
return v.x * v.y
}
// ポインタレシーバのScaleメソッド
func (v *Vertex) Scale(i int) {
v.x = v.x * i
v.y = v.y * i
}
ここでArea
はVertex
型のインスタンスのコピーを受け取る関数であり、Scale
はVertex
型のインスタンスのポインタに対応する関数です
データ型の違いは、関数のそれぞれの役割を表しています
インスタンスのコピーを渡すということは、コピーされたインスタンスに対する変更が元のインスタンスには反映されないことを意味します
逆にポインタを渡す場合はインスタンスの格納場所を参照させることになるため、インスタンスの値を直接書き換えられる状態であることを示します
つまりポインタを値渡しすれば、インスタンスはコピーされません
Scale
はポインタを通じてインスタンス内の値を書き換える関数であると分かります
対するArea
はコピーを受け取るのでこれがインスタンスの値を変更する目的の関数でないと読み取れます
この関数の役目はインスタンス内のメンバの値を掛け合わせた結果を返すことです
ここまでの内容を振り返り、改めてmain
の手続きに注目します
func main() {
v := New(3, 4) //Vertexインスタンスを作成
v.Scale(10)
fmt.Println(v.Area())
}
New
でポインタを獲得し、Scale
で変更し、Area
で結果を返すという、一連の目的に沿ったプログラムになっていることが分かります
なお、コピーが渡せるならば、New
の実際の戻り値はポインタでなくとも構わないのではないかと思うかもしれません
これは実際にその通りです
しかし、その場合インスタンスのコピーによる計算負荷を考慮する必要があります
ポインタであれば生成したばかりのインスタンスをそのまま使い回せます
とはいえヒープで扱うよりスタックでインスタンスを管理する方が速度的には優位です
計算に必要な空間量を鑑みて適切な使い分けを心掛けましょう