🌊

Goのポインタについて

2023/01/18に公開

ポインタはGoの基本的な概念で、プログラム内で値やレコードへの参照を渡すことができるようになります。
Goの重要な機能となります。

Goでは、ポインタは他の変数のメモリアドレスを格納する変数です。ポインタを作るには、&演算子を使って変数のアドレスを取得します。

package main

import "fmt"

func main() {
	x := 10
	var p *int = &x

	fmt.Println(p)  // x のメモリアドレスを出力します
	fmt.Println(*p) // x の値を出力します
}

上記のコードでは、値10を持つ変数xとxへのポインタpを作成し、&演算子でpに格納されているxのメモリアドレスを返しています。
演算子を使ってポインタの参照を解除し、そのポインタが指す値にアクセスすることができます。上記のコードでは、*pはxの値、つまり10を返します。

ポインタは、関数同士で値への参照を渡すために使用することができます。

package main

import "fmt"

func addOne(x *int) {
	*x = *x + 1
}

func main() {
	x := 10
	addOne(&x)
	fmt.Println(x) 
}

上記のコードでは、addOne関数にxのメモリアドレスをポインタとして渡しています。関数内部では、*演算子を使ってポインタをデリファレンスし、xの値を変更しています。

メソッドにおけるポインタのレシーバー。Goでは、ポインタを含む任意の型に対してメソッドを定義することができます。ポインタのレシーバを持つメソッドを定義した場合、そのメソッドはレシーバの値を変更できることを意味します。

package main

import "fmt"

type MyStruct struct {
	x int
}

func (s *MyStruct) increment() {
	s.x++
}

func main() {
	s := MyStruct{x: 10}
	s.increment()
	fmt.Println(s.x) 
}

上記のコードでは、ポインタ・レシーバを持つMyStructのメソッドincrementを定義しています。もし、このメソッドを値のレシーバで定義していたら(例:func (s MyStruct) increment())、sの値を変更することはできません。

ポインタ型

Goでは、他の型へのポインタである新しい型を定義することができます。

package main

import "fmt"

type MyInt int
type MyIntPointer *MyInt

func main() {
	var x MyInt = 10
	var p MyIntPointer = &x
	fmt.Println(*p)
}

上記のコードでは、MyIntへのポインタであるMyIntPointerという新しい型を定義しています。そして、MyIntPointerを他のポインタ型と同様に使用することができます。

構造体へのポインタ

構造体へのポインタを使用すると、関数の引数として渡すときに大きな構造体をコピーする必要がなくなります。

package main

import "fmt"

type MyStruct struct {
    x int
    y int
}

func updateStruct(s *MyStruct) {
    s.x++
    s.y++
}

func main() {
    s := MyStruct{x: 10, y: 20}
    updateStruct(&s)
    fmt.Println(s) 
}

上記のコードでは、updateStruct関数にsへのポインタを渡しています。これにより、構造体全体をコピーする必要がなくなり、大きな構造体の場合に効率的です。

ポインタは、Goの軽量な実行スレッドであるゴルーチン間でデータを共有するために使用されることもあります。ポインタをゴルーチンに渡すと、複数のゴルーチンから同じデータにアクセスし、変更することができます。

package main

import (
	"fmt"
	"sync"
)

type MyStruct struct {
	x int
	y int
}

func updateStruct(s *MyStruct, wg *sync.WaitGroup) {
	s.x++
	s.y++
	wg.Done()
}

func main() {
	s := MyStruct{x: 10, y: 20}
	var wg sync.WaitGroup

	wg.Add(2)
	go updateStruct(&s, &wg)
	go updateStruct(&s, &wg)
	wg.Wait()

	fmt.Println(s)
}

上記のコードでは、xとyという2つのフィールドを持つMyStruct構造体を作成し、構造体へのポインタを使用してxとyを増分するゴルーチンを2つ作成します。sync.WaitGroupを使用して、両方のゴルーチンが終了したことを確認してから、最終的なsの値を出力しています。

各ゴルーチンにsとsync.WaitGroupへのポインタを渡していることに注意してください。これによって、各ゴルーチンは同じデータにアクセスし、変更することができます。もし、ポインタの代わりにsとsync.WaitGroup の値を渡していたら、ゴルーチンはデータのコピーで動作し、元の値は変更されません。

Goにはガベージコレクタがあり、不要になったメモリを自動的に解放してくれることです。C言語などのように、使い終わったら手動でメモリを解放する心配がありません。

この記事が、Goでポインタがどのように機能するかを理解する助けになったら幸いです。

Discussion