見出し画像

Golang_ポインタ型と参照型 #460

前回の記事でGolangの参照渡し(ポインタ型)について整理しました。

その中で、参照渡しを使う場面の1つで「他の参照型を使う場合」を紹介しています。上の記事では簡単に触れる程度でしたが、ここで少し掘り下げてみたいと思います。

ポインタ型とは

参照型について理解を深める前に、ポインタ型の概要を改めて掴んでおきます。

前回の記事で触れたように、ポインタ型は変数のメモリアドレスを格納するデータ型です。ポインタを使うことで、メモリアドレスを直接操作し、そのアドレスが指す実際のデータにアクセスできます。

例えば(前回同様ですが)以下のコードでは、*intがポインタ型を表しています。

package main

import "fmt"

func addOne(num *int) {
    *num += 1   // ポインタ型の変数に「*」を付けるとそのアドレスの値にアクセスできる(デリファレンス)
}

func main() {
    x := 10
    addOne(&x)  // 変数に「&」を付けるとポインタを渡せる(ここではxのポインタを渡す)
    fmt.Println("In main:", x)  // x自体にaddOneが作用して11になる
}

ポインタ型は大きなデータ構造を効率的に扱いたい場合(コピーを避けてメモリを節約したい場合)や、複数の変数を関数内で変更してしまいたい場合などに有効です。

参照型

参照型は、データへの参照を隠蔽して扱うデータ型です。ポインタ型と振る舞いが似ていますが異なる概念になっています。

参照型の変数を別の変数に代入したり関数に渡したりすると、値のコピーではなく参照が渡されます。この性質によって、参照型では複数の場所から同一のデータ構造にアクセスして操作することが可能です。

参照型は、例えばスライス, マップ, インターフェースやチャネルがあります。

スライスで例示すると以下のようになります。関数に渡して処理を施すと、元の配列が更新されます。

func appendSlice(slice []int) {
    slice = append(slice, 4)
}

func main() {
    mySlice := []int{1, 2, 3}
    appendSlice(mySlice)
    fmt.Println(mySlice) // 出力: [1, 2, 3, 4]
}

これを見ると参照型は複数のポインタが集まっている型なのか?と感じますが、実際は少し異なります。理解のため、代表的な参照型の内部構造について触れたいと思います。

スライス

スライスは他の多くのプログラミング言語で言うところの配列に相当します。これは3つのコンポーネントから成り立っています。

  1. ポインタ:スライスの最初の要素を指すポインタ(スライスが参照している配列の実際のデータ)

  2. 長さ:スライスが現在持っている要素の数

  3. 容量:スライスのポインタが指す配列の要素の総数

スライスの操作では、基本的にこれらのポインタを通じて配列の部分的なビューを提供しています。つまりスライスは配列への参照を持つと言えます。

スライスはポインタ以外の要素も持っているので、単純にポインタを並べたものがスライス、というわけではなさそうですね。

マップ

マップはキーと値のペアを格納する連想配列またはハッシュテーブルです。以下の特徴を持ちます。

  • マップはキーと値のペアを内部のハッシュテーブルに保存する

  • このテーブルは必要に応じて動的にサイズ調整される

  • マップ内の値は直接値として格納されるか、ポインタとして格納されるかのどちらかで、この選択は値のサイズと型による(大きなデータならポインタになる等)

特徴の3つ目でポインタが使われていますが、これとは別に、マップを関数に渡した場合にはそのマップへのポインタが渡されることになります。そのため、スライスと同様に関数内でマップを変更すると、その変更は元のマップに適用されます。

こちらも単純にポインタのみで構成されているわけではありません。

インターフェース

インターフェースはある型が持つべきメソッドのシグネチャを定義して抽象化するもので、これも参照型です。詳細は今後のブログでまとめますが、インターフェースは元のデータへの参照を保持するため、関数内でインターフェースを通じてデータが変更されると、その変更は呼び出し元にも影響を与えます。

チャネル

チャネルはゴルーチン間でデータを安全に送受信するための通信メカニズムで、参照型です。こちらも詳細は今後のブログでまとめますが、チャネルを使うことで、複数のゴルーチン間でのデータ共有が容易になり、Goの特徴でもある並行処理を支えています。


Golangにおけるポインタ型と参照型について整理しました。
ここまでお読みいただきありがとうございました!

参考



この記事が気に入ったらサポートをしてみませんか?