Goにポインターがあることの意味は何ですか?


98

Goのポインターは関数の引数の変更を許可することを知っていますが、(適切なconstまたはmutable修飾子を使用して)参照のみを採用した方が簡単ではなかったでしょう。これで、ポインターが用意され、マップやチャネルなどの一部の組み込み型については、暗黙的に参照渡しされます。

何かが足りないのですか、それともGoのポインターが不必要な複雑なものなのですか?


1
明確にするのに役立つ質問を次に示します。stackoverflow.com/ questions/ 795160 / 値による参照の受け渡しと、参照による真の受け渡しには違いがあります。
R.マルティーニョフェルナンデス

1
注:質問はJavaに関するものですが、ここでも当てはまります。
R.マルティーニョフェルナンデス

1
「そして、マップやチャネルなどの一部の組み込み型の場合、暗黙的に参照渡しされます。」いいえ、すべてがGoの値渡しです。一部の型は内部で変更可能な状態を持っているため、(非公式には)参照型です。
newacct 2012

この質問の問題は、「参照」が明確に定義されたプロパティを持つ単一のものではないことです。「参照」という用語は非常にあいまいです。回答の中で、「参照」という言葉にどれだけ多くの人が異なるものを読んでいるかがわかります。したがって、この質問では、Goポインターと質問の参照の間にどのような違いがあるかを詳しく説明する必要があります。
mtraceur

回答:


36

私はhttp://www.golang-book.com/8から取られた例が本当に好きです

func zero(x int) {
    x = 0
}
func main() {
    x := 5
    zero(x)
    fmt.Println(x) // x is still 5
}

と対照的に

func zero(xPtr *int) {
    *xPtr = 0
}
func main() {
    x := 5
    zero(&x)
    fmt.Println(x) // x is 0
}

41
問題は、「なぜ参照の代わりにポインターがあるのか​​」ということでした。なぜこの例が参照で機能しないのか理解できません。
AndreKR 2016年

@AndreKR参照渡しにするか値渡しにするかを選択できるためです。両方が望ましい場合もあります。
JDSweetBeat 2016

9
@DJMethaneMan「ポインターと値渡し」ではなく、「ポインターと参照」です!
AndreKR 2016

副次的なコメントとして、参照渡しは「ref」キーワードを介してC#2.0で追加されました。もちろん、特定のケースでは、ポインタがさらに便利です。これは、ポインタトゥポインタトゥポインタポインタを使用できるためです
ロビーファン2017

33

ポインタはいくつかの理由で役立ちます。ポインタを使用すると、メモリレイアウトを制御できます(CPUキャッシュの効率に影響します)。Goでは、すべてのメンバーが連続したメモリにある構造を定義できます。

type Point struct {
  x, y int
}

type LineSegment struct {
  source, destination Point
}

この場合、Point構造は構造内に埋め込まれますLineSegment。ただし、常にデータを直接埋め込むことはできません。バイナリツリーやリンクリストなどの構造をサポートする場合は、ある種のポインタをサポートする必要があります。

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode
}

Java、Pythonなどでは、複合型を埋め込むことができないため、この問題はありません。したがって、埋め込みとポインティングを構文的に区別する必要はありません。

Goポインターで解決されたSwift / C#構造体の問題

同じことを達成するための可能な代替策は、C#とSwiftがそうするように、structとを区別するclassことです。しかし、これには制限があります。通常、関数が構造体をinoutパラメーターとして受け取ることを指定して、構造体のコピーを回避できますが、構造体への参照(ポインター)を格納することはできません。これは、たとえばプールアロケーターを作成する(以下を参照)場合など、構造体を参照型として扱うことができないことを意味します。

カスタムメモリアロケータ

ポインタを使用して、独自のプールアロケータを作成することもできます(これは、原則を示すために多数のチェックを削除して非常に簡略化されています)。

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode

  nextFreeNode *TreeNode; // For memory allocation
}

var pool [1024]TreeNode
var firstFreeNode *TreeNode = &pool[0] 

func poolAlloc() *TreeNode {
    node := firstFreeNode
    firstFreeNode  = firstFreeNode.nextFreeNode
    return node
}

func freeNode(node *TreeNode) {
    node.nextFreeNode = firstFreeNode
    firstFreeNode = node
}

2つの値を交換する

ポインタを使用すると、を実装することもできますswap。それは2つの変数の値を交換しています:

func swap(a *int, b *int) {
   temp := *a
   *a = *b
   *b = temp
}

結論

メモリのレイアウトと使用を制御する機能がないために、パフォーマンスを同じ範囲に調整できないため(キャッシュミスはパフォーマンスに大きく影響します)、JavaなどのシステムプログラミングでC ++を完全に置き換えることはできませんでした。Goは多くの領域でC ++を置き換えることを目的としており、そのためポインターをサポートする必要があります。


7
C#では、構造体を参照で渡すことができます。「ref」および「out」キーワードを参照してください。
olegz 2015年

1
さて、それはスイフトのようです。私の例を更新する方法を考えます。
Erik Engheim、2015年

29

参照は再割り当てできませんが、ポインターは再割り当てできます。これだけでも、参照を使用できない多くの状況でポインタが役立ちます。


17
参照が再割り当て可能かどうかは、言語固有の実装の問題です。
crantok 2016

28

Goは、簡潔でシンプルな言語になるように設計されています。したがって、それは値とポインタだけで始まりました。その後、必要に応じて、いくつかの参照タイプ(スライス、マップ、チャネル)が追加されました。


Goプログラミング言語:言語設計に関するFAQ:配列が値であるのに、マップ、スライス、チャネルが参照されるのはなぜですか?

「そのトピックには多くの歴史があります。初期の段階では、マップとチャネルは構文的にポインターであり、非ポインターインスタンスを宣言したり使用したりすることは不可能でした。また、配列がどのように機能するかについて苦労しました。最終的に、厳密な分離を決定しましたポインタと値の組み合わせにより、言語が使いにくくなりました。配列の参照形式を処理するためのスライスを含む参照型を導入することで、これらの問題が解決されました。参照型は、言語にいくつかの残念な複雑さを追加しますが、使いやすさに大きな影響を与えます。彼らが導入されたとき、より生産的で快適な言語です。」


高速コンパイルは、Goプログラミング言語の主要な設計目標です。それにはコストがかかります。犠牲者の1つは、変数(基本的なコンパイル時定数を除く)とパラメーターを不変としてマークする機能です。要求されたが断られた。


golang-nuts:言語を使います。いくつかのフィードバックと疑問。

「型システムにconstを追加すると、どこにでも強制的に表示され、何かが変更された場合は強制的に削除されます。何らかの方法でオブジェクトを不変にマークすることにはいくつかの利点があるかもしれませんが、const型修飾子はそうではないと思いますトーゴ。"


FWIW、Goの「参照タイプ」も再割り当て可能です。それらは暗黙のポインタのようなものですか?
Matt Joiner、

1
これらは、ポインター(および長さ、容量など)を含む構造体の特別な構文です。
mk12 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.