Warum sollte ich () oder new () machen?


203

In den Einführungsdokumenten werden viele Absätze zur Erläuterung des Unterschieds zwischen new()und verwendet make(). In der Praxis können Sie jedoch Objekte im lokalen Bereich erstellen und zurückgeben.

Warum sollten Sie das Allokatorpaar verwenden?

Antworten:


170

Dinge, die Sie damit machen können make, können Sie nicht anders machen:

  • Erstellen Sie einen Kanal
  • Erstellen Sie eine Karte mit vorab zugewiesenem Speicherplatz
  • Erstellen Sie ein Slice mit vorab zugewiesenem Speicherplatz oder mit len! = Cap

Es ist etwas schwieriger zu rechtfertigen new. Die Hauptsache, die es einfacher macht, ist das Erstellen von Zeigern auf nicht zusammengesetzte Typen. Die beiden folgenden Funktionen sind äquivalent. Man ist nur etwas prägnanter:

func newInt1() *int { return new(int) }

func newInt2() *int {
    var i int
    return &i
}

41
Es ist wahr, dass 'neu' nicht zum Erstellen von Kanälen verwendet werden kann. Meiner Meinung nach lautet der Punkt jedoch: Was würde passieren, wenn 'new' und 'make' zu einer einzigen integrierten Funktion zusammengefügt werden? Ein solcher Ersatz wäre sicherlich möglich. Da dies möglich ist, stellt sich die Frage: Was sind die objektiven Gründe für 2 integrierte Funktionen und nicht nur für eine verallgemeinerte integrierte Funktion? - Ihre Antwort besagt korrekt, dass "neu" nicht zum Erstellen von Kanälen / Karten / Slices verwendet werden kann, liefert jedoch keine Rechtfertigung dafür, warum Go "neu" und "machen" hat, anstatt 1 verallgemeinerte Zuweisungs- + Init-Funktion zu haben.

5
Sie konnten kombiniert werden und es wurde sogar von Rob Pike an einem Punkt vorgeschlagen: groups.google.com/d/topic/golang-nuts/kWXYU95XN04/discussion . Letztendlich ging es aus ähnlichen Gründen wie in Ihrer Antwort nicht durch.
Evan Shaw

12
Effektives go macht den Punkt, dass new einen Wert von Null zurückgibt, während map Nicht-Null-Typen Map, Slice oder Channel zuweist. Siehe golang.org/doc/effective_go.html#allocation_new
kristianp

Was ist mit m := map[string]int{}statt m := make(map[string]int)? Es ist nicht erforderlich, die Größe vorab zuzuweisen.
Noam Manos

165

Go bietet mehrere Möglichkeiten zur Speicherzuweisung und Wertinitialisierung:

&T{...}, &someLocalVar, new,make

Die Zuordnung kann auch beim Erstellen von zusammengesetzten Literalen erfolgen.


newkann verwendet werden, um Werte wie Ganzzahlen zuzuweisen, &intist illegal:

new(Point)
&Point{}      // OK
&Point{2, 3}  // Combines allocation and initialization

new(int)
&int          // Illegal

// Works, but it is less convenient to write than new(int)
var i int
&i

Der Unterschied zwischen newund makelässt sich anhand des folgenden Beispiels erkennen:

p := new(chan int)   // p has type: *chan int
c := make(chan int)  // c has type: chan int

Angenommen, Go hat kein newund make, aber es hat die eingebaute Funktion NEW. Dann würde der Beispielcode folgendermaßen aussehen:

p := NEW(*chan int)  // * is mandatory
c := NEW(chan int)

Das * wäre obligatorisch , also:

new(int)        -->  NEW(*int)
new(Point)      -->  NEW(*Point)
new(chan int)   -->  NEW(*chan int)
make([]int, 10) -->  NEW([]int, 10)

new(Point)  // Illegal
new(int)    // Illegal

Ja, Zusammenführung newund makein eine einzige integrierte Funktion ist möglich. Es ist jedoch wahrscheinlich, dass eine einzelne integrierte Funktion bei neuen Go-Programmierern zu mehr Verwirrung führen würde als zwei integrierte Funktionen.

Alle oben genannten Punkte in Anbetracht, scheint es besser geeignet für newund makegetrennt zu bleiben.


@TorstenBronger Ich finde neu einfacher zu lesen und zeige, dass dies die Instanz ist, in der intdas erstellt wird.
Daniel Toebe

4
Wolltest du schreiben make(Point)und make(int)in diesen letzten 2 Zeilen?
Jimmy Huch

26

makeDie Funktion weist nur ein Objekt vom Typ Slice, Map oder Chan zu und initialisiert es. Wie newdas erste Argument ist ein Typ. Es kann aber auch ein zweites Argument sein, die Größe. Im Gegensatz zu new ist der Rückgabetyp von make derselbe wie der Typ seines Arguments und kein Zeiger darauf. Und der zugewiesene Wert wird initialisiert (nicht wie in neu auf Null gesetzt). Der Grund ist, dass Slice, Map und Chan Datenstrukturen sind. Sie müssen initialisiert werden, sonst können sie nicht verwendet werden. Dies ist der Grund, warum new () und make () unterschiedlich sein müssen.

Die folgenden Beispiele aus Effective Go machen dies sehr deutlich:

p *[]int = new([]int) // *p = nil, which makes p useless
v []int = make([]int, 100) // creates v structure that has pointer to an array, length field, and capacity field. So, v is immediately usable

1
In new([]int)weist es nur Speicher für [] int zu, initialisiert es jedoch nicht, sodass es einfach zurückkehrt nil. nicht der Zeiger auf den Speicher, weil er unbrauchbar ist. make([]int)ordnet und initialisiert, damit es verwendet werden kann, und gibt dann seine Adresse zurück.
o0omycomputero0o

12
  • new(T)- Ordnet Speicher zu und setzt ihn auf den Wert Null für Typ T ..
    ..das gilt 0für int , ""für Zeichenfolge und nilfür referenzierte Typen ( Slice , Map , Chan ).

    Beachten Sie, dass referenzierte Typen nur Zeiger auf einige zugrunde liegende Datenstrukturen sind , die nicht durch new(T)
    Beispiel erstellt werden : Im Falle eines Slice wird das zugrunde liegende Array nicht erstellt, sodass new([]int) ein Zeiger auf nichts zurückgegeben wird

  • make(T)- Ordnet Speicher für referenzierte Datentypen ( Slice , Map , Chan ) zu und initialisiert die zugrunde liegenden Datenstrukturen

    Beispiel: Im Falle eines Slice wird das zugrunde liegende Array mit der angegebenen Länge und Kapazität erstellt.
    Beachten Sie, dass ein Array im Gegensatz zu C ein primitiver Typ in Go!


Davon abgesehen:

  • make(T) verhält sich wie eine Composite-Literal-Syntax
  • new(T)verhält sich wie var(wenn die Variable nicht initialisiert ist)

    func main() {
        fmt.Println("-- MAKE --")
        a := make([]int, 0)
        aPtr := &a
        fmt.Println("pointer == nil :", *aPtr == nil)
        fmt.Printf("pointer value: %p\n\n", *aPtr)
    
        fmt.Println("-- COMPOSITE LITERAL --")
        b := []int{}
        bPtr := &b
        fmt.Println("pointer == nil :", *bPtr == nil)
        fmt.Printf("pointer value: %p\n\n", *bPtr)
    
        fmt.Println("-- NEW --")
        cPtr := new([]int)
        fmt.Println("pointer == nil :", *cPtr == nil)
        fmt.Printf("pointer value: %p\n\n", *cPtr)
    
        fmt.Println("-- VAR (not initialized) --")
        var d []int
        dPtr := &d
        fmt.Println("pointer == nil :", *dPtr == nil)
        fmt.Printf("pointer value: %p\n", *dPtr)
    }

    Führen Sie das Programm aus

    -- MAKE --
    pointer == nil : false
    pointer value: 0x118eff0  # address to underlying array
    
    -- COMPOSITE LITERAL --
    pointer == nil : false
    pointer value: 0x118eff0  # address to underlying array
    
    -- NEW --
    pointer == nil : true
    pointer value: 0x0
    
    -- VAR (not initialized) --
    pointer == nil : true
    pointer value: 0x0

    Weiterführende Literatur:
    https://golang.org/doc/effective_go.html#allocation_new https://golang.org/doc/effective_go.html#allocation_make


  • Mit einem Beispiel wird es klarer. upvoted :)
    Sumit Jha

    8

    Sie müssen make()Kanäle und Karten (und Slices, aber diese können auch aus Arrays erstellt werden) erstellen. Es gibt keine alternative Möglichkeit, diese zu erstellen, sodass Sie sie nicht make()aus Ihrem Lexikon entfernen können.

    Was new(), weiß ich nicht , aus welchem Grunde auch freihändig , warum Sie benötigen es , wenn Sie struct Syntax verwenden. Es hat jedoch eine eindeutige semantische Bedeutung: "Erstellen und Zurückgeben einer Struktur mit allen Feldern, die auf ihren Nullwert initialisiert wurden", was nützlich sein kann.


    1
    Daher sollte Neu vermieden werden und nur die Verwendung der Struct-Syntax bevorzugen
    CommonSenseCode

    8

    Abgesehen von allem, was in Effective Go erklärt wird , besteht der Hauptunterschied zwischen new(T)und &T{}darin, dass letzteres explizit eine Heap-Zuweisung durchführt. Es ist jedoch zu beachten, dass dies implementierungsabhängig ist und sich daher ändern kann.

    Ein Vergleich makemit newmacht wenig Sinn, da beide völlig unterschiedliche Funktionen ausführen. Dies wird jedoch im verlinkten Artikel ausführlich erläutert.


    10
    Die Behauptung, die &T{}explizit eine Heap-Zuweisung durchführt, ist AFAIK und basiert nicht auf irgendetwas in den Spezifikationen. Eigentlich glaube ich, dass die Escape-Analyse solche * T, wann immer möglich, genau so wie bei auf Stapel hält new(T).
    zzzz

    6

    new (T): Gibt einen Zeiger auf Typ T zurück, einen Wert vom Typ * T, weist den Speicher zu und setzt ihn auf Null. new (T) entspricht & T {} .

    make (T): Gibt einen initialisierten Wert vom Typ T zurück . Es ordnet den Speicher zu und initialisiert ihn. Es wird für Slices, Karten und Kanäle verwendet.

    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.