🌀

【Go】ポインタについてよくわからなかったのでまとめてみた

に公開

はじめにまとめ

  • &はアドレス演算子。変数のアドレスを取得するときに使われる
  • *は、ポインタ型の変数の前につけるとその変数(アドレス)に保存されている値を返す

きっかけ

&*、どっちがどっちで何だっけ」という状態だったのでちゃんと習得したかったから

見ていく

基本的な動き

x := 10
pointerToX := &x // xのアドレスが代入される
fmt.Println(pointerToX)  // 0xc00009a008
fmt.Println(*pointerToX) // 10

&*の関係を図にしてみました。これでこんがらがることもなさそう?

ポインタを返す関数

Goのコードを見ていると、ポインタを返す関数があることに気づきました。

これはポインタを渡すことで、呼び出し元で宣言された変数に間接的に変更を加えることができるというメリットがあるようです。

実際に値を返す関数とポインタを返す関数を用意して比較してみたいと思います。

type Counter struct {
  Count int
}

// 1. 値を返す
func NewCounterByValue() Counter {
  return Counter{Count: 0}
}

// 2. ポインタを返す
func NewCounterByPointer() *Counter {
  return &Counter{Count: 0}
}

それぞれの関数によって生成されたCounter構造体に変化を加えてみます。

// 1. 値を返す関数
c1 := NewCounterByValue()
c2 := c1 // 値のコピー
c2.Count++
fmt.Println(c1.Count == c2.Count) // false (c1の値は0のまま)

// 2. ポインタを返す関数
p1 := NewCounterByPointer()
p2 := p1 // ポインタのコピー
p2.Count++
fmt.Println(p1.Count == p2.Count) // true

1の関数では、返り値を複製したものに++を行って1を加算していますが、c1.Count == c2.Countはfalseになってしまいます。
しかし2の関数では同様の処理を行っているのに結果がtrueとなります。

これは2の関数ではポインタが渡されているため、p2の変更がp1にも反映されるのに対して、1の関数では値のコピーとなっているため、c2への変更がc1に反映されないということになります。

標準パッケージのコードで試してみる

osパッケージのCreate関数は返り値にポインタ型を含んでいます

func Create(name string) (*File, error)

この関数で先ほどの「2の関数」でやったことを試してみます。行うことを書き出すと

  1. 空のファイルを作成して変数に代入
  2. その変数を別の変数に代入
  3. 最初の変数にHello, と書き込む
  4. 次の変数にWorld!と書き込む
  5. ファイルの文字列を出力するとHello, World!となる

func main() {
	file, err := os.Create("trial.txt")
	if err != nil {
		fmt.Println("Error creating file:", err)
		return
	}
	defer file.Close()

	copiedFile := file

	_, err = file.WriteString("Hello, ")
	if err != nil {
		fmt.Println("Error writing to file:", err)
		return
	}

	_, err = copiedFile.WriteString("World!")
	if err != nil {
		fmt.Println("Error writing to copied file:", err)
		return
	}

	content, err := os.ReadFile("trial.txt")
	if err != nil {
		fmt.Println("Error reading file:", err)
		return
	}
	fmt.Println(string(content))
}

ファイルを実行すると以下の通りとなります

$ go run .
Hello, World!

おさらい

  • &はアドレス演算子。変数のアドレスを取得するときに使われる
  • *は、ポインタ型の変数の前につけるとその変数(アドレス)に保存されている値を返す

余談

久々に技術記事を書いたんですが、知識が定着する感じがしますね!

参考資料

Discussion