Was bringt es, Zeiger in Go zu haben?


98

Ich weiß, dass Zeiger in Go die Mutation der Argumente einer Funktion ermöglichen, aber wäre es nicht einfacher gewesen, wenn sie nur Referenzen (mit geeigneten const- oder veränderlichen Qualifikationsmerkmalen) übernommen hätten. Jetzt haben wir Zeiger und für einige eingebaute Typen wie Karten und Kanäle wird implizit als Referenz übergeben.

Vermisse ich etwas oder sind Zeiger in Go nur eine unnötige Komplikation?


1
Hier ist eine Frage, die zur Verdeutlichung beitragen kann: stackoverflow.com/questions/795160/… Es gibt einen Unterschied zwischen der Übergabe von Referenzen nach Wert und der Übergabe von Referenzen nach Referenz.
R. Martinho Fernandes

1
Hinweis: Die Frage bezieht sich auf Java, gilt aber auch hier.
R. Martinho Fernandes

1
"und für einige integrierte Typen wie Karten und Kanäle implizite Referenzübergabe." Nein, in Go ist alles pass-by-value. Einige Typen sind (informell als) Referenztypen, da sie einen internen veränderlichen Zustand haben.
Newacct

Das Problem bei dieser Frage ist, dass "Referenzen" keine einzige Sache mit genau definierten Eigenschaften sind. Der Begriff "Referenzen" ist sehr vage. Wir können in den Antworten sehen, wie viele Menschen verschiedene Dinge in das Wort "Referenzen" einlesen. Diese Frage sollte also genau erläutern, welche Unterschiede zwischen Go-Zeigern und den Referenzen bestehen, die die Frage im Auge hat.
mtraceur

Antworten:


36

Ich mag ein Beispiel aus 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
}

im Gegensatz zu

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

41
Die Frage war "Warum haben wir Zeiger anstelle von Referenzen " und ich verstehe nicht, warum dieses Beispiel nicht mit Referenzen funktioniert.
AndreKR

@AndreKR Weil wir wählen können, ob wir als Referenz oder als Wert übergeben möchten. Es gibt einige Fälle, in denen beides wünschenswert sein kann.
JDSweetBeat

9
@DJMethaneMan Es ist "Zeiger gegen Referenzen", nicht "Zeiger gegen Wertübergabe"!
AndreKR

Als Nebenkommentar wurde in C # 2.0 über das Schlüsselwort "ref" eine Referenzübergabe hinzugefügt. Natürlich sind Zeiger in bestimmten Fällen noch bequemer, weil wir Zeiger auf Zeiger auf Zeiger haben können ...
Robbie Fan

33

Zeiger sind aus mehreren Gründen nützlich. Zeiger ermöglichen die Kontrolle über das Speicherlayout (wirkt sich auf die Effizienz des CPU-Cache aus). In Go können wir eine Struktur definieren, in der sich alle Mitglieder im zusammenhängenden Speicher befinden:

type Point struct {
  x, y int
}

type LineSegment struct {
  source, destination Point
}

In diesem Fall sind die PointStrukturen in die Struktur eingebettet LineSegment. Sie können Daten jedoch nicht immer direkt einbetten. Wenn Sie Strukturen wie Binärbäume oder verknüpfte Listen unterstützen möchten, müssen Sie eine Art Zeiger unterstützen.

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

Java, Python usw. haben dieses Problem nicht, da Sie keine zusammengesetzten Typen einbetten können, sodass syntaktisch nicht zwischen Einbetten und Zeigen unterschieden werden muss.

Probleme mit Swift / C # -Strukturen, die mit Go-Zeigern behoben wurden

Eine mögliche Alternative, um dasselbe zu erreichen, besteht darin, zwischen structund classwie C # und Swift zu unterscheiden. Dies hat jedoch Einschränkungen. Während Sie normalerweise angeben können, dass eine Funktion eine Struktur alsinout Parameter verwendet, um das Kopieren der Struktur zu vermeiden, können Sie keine Verweise (Zeiger) auf Strukturen speichern. Dies bedeutet, dass Sie eine Struktur niemals als Referenztyp behandeln können, wenn Sie dies nützlich finden, z. B. um einen Pool-Allokator zu erstellen (siehe unten).

Benutzerdefinierter Speicherzuweiser

Mithilfe von Zeigern können Sie auch Ihren eigenen Pool-Allokator erstellen (dies ist sehr vereinfacht, da viele Überprüfungen entfernt wurden, um nur das Prinzip zu zeigen):

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
}

Tauschen Sie zwei Werte aus

Mit Zeigern können Sie auch implementieren swap. Das heißt, die Werte zweier Variablen werden ausgetauscht:

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

Fazit

Java war nie in der Lage, C ++ für die Systemprogrammierung an Orten wie Google vollständig zu ersetzen, auch weil die Leistung nicht in gleichem Maße angepasst werden kann, da das Speicherlayout und die Speicherauslastung nicht gesteuert werden können (Cache-Fehler beeinträchtigen die Leistung erheblich). Go hat sich zum Ziel gesetzt, C ++ in vielen Bereichen zu ersetzen und muss daher Zeiger unterstützen.


7
Mit C # können Strukturen als Referenz übergeben werden. Siehe Schlüsselwörter "ref" und "out".
Olegz

1
Okay, es ist wie bei Swift. Ich werde über eine Möglichkeit nachdenken, mein Beispiel zu aktualisieren.
Erik Engheim

29

Referenzen können nicht neu zugewiesen werden, Zeiger dagegen. Dies allein macht Zeiger in vielen Situationen nützlich, in denen Referenzen nicht verwendet werden konnten.


17
Ob Referenzen neu zugewiesen werden können, ist ein sprachspezifisches Implementierungsproblem.
Crantok

28

Go ist als knappe, minimalistische Sprache konzipiert. Es begann daher nur mit Werten und Zeigern. Später wurden notwendigerweise einige Referenztypen (Slices, Karten und Kanäle) hinzugefügt.


Die Go-Programmiersprache: Sprachdesign FAQ: Warum verweisen Karten, Slices und Kanäle, während Arrays Werte sind?

"Es gibt viel Geschichte zu diesem Thema. Schon früh waren Karten und Kanäle syntaktisch Zeiger und es war unmöglich, eine Nicht-Zeiger-Instanz zu deklarieren oder zu verwenden. Außerdem hatten wir Probleme damit, wie Arrays funktionieren sollten. Schließlich entschieden wir uns für die strikte Trennung Durch die Einführung von Referenztypen, einschließlich Slices zur Behandlung der Referenzform von Arrays, wurden diese Probleme behoben. Referenztypen fügten der Sprache eine bedauerliche Komplexität hinzu, haben jedoch einen großen Einfluss auf die Benutzerfreundlichkeit: Go wurde zu einem produktivere, bequemere Sprache, als sie eingeführt wurden. "


Schnelle Kompilierung ist ein wichtiges Designziel der Programmiersprache Go. das hat seine kosten. Eines der Opfer scheint die Fähigkeit zu sein, Variablen (mit Ausnahme grundlegender Kompilierungszeitkonstanten) und Parameter als unveränderlich zu markieren. Es wurde angefordert, aber abgelehnt.


Golang-Nüsse: Geh Sprache. Einige Rückmeldungen und Zweifel.

"Das Hinzufügen von const zum Typsystem erzwingt, dass es überall angezeigt wird, und zwingt einen, es überall zu entfernen, wenn sich etwas ändert. Das Markieren von Objekten, die auf irgendeine Weise unveränderlich sind, kann zwar von Vorteil sein, wir glauben jedoch nicht, dass ein const-Typ-Qualifizierer zu weit entfernt ist gehen."


FWIW, "Referenztypen" in Go sind ebenfalls neu zuweisbar. Sie sind eher implizite Zeiger?
Matt Joiner

1
Sie sind nur eine spezielle Syntax für Strukturen, die einen Zeiger (und Länge, Kapazität, ...) enthalten.
mk12
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.