Perché dovrei fare () o nuovo ()?


203

I documenti introduttivi dedicano molti paragrafi alla spiegazione della differenza tra new()e make(), ma in pratica, è possibile creare oggetti nell'ambito locale e restituirli.

Perché dovresti usare la coppia di allocatori?

Risposte:


170

Le cose che puoi fare makeche non puoi fare in nessun altro modo:

  • Crea un canale
  • Crea una mappa con spazio preallocato
  • Crea una sezione con spazio preallocato o con len! = Cap

È un po 'più difficile giustificare new. La cosa principale che semplifica è la creazione di puntatori a tipi non compositi. Le due funzioni seguenti sono equivalenti. Uno è solo un po 'più conciso:

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

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

41
È vero che "nuovo" non può essere utilizzato per creare canali. Tuttavia, a mio avviso, il punto è: cosa accadrebbe se "nuovo" e "crea" si unissero in un'unica funzione integrata? Certamente, una tale sostituzione sarebbe possibile. Dal momento che è possibile, la domanda è: quali sono le ragioni oggettive per avere 2 funzioni integrate anziché solo 1 funzione integrata generalizzata. - La tua risposta afferma correttamente che "nuovo" non può essere usato per creare canali / mappe / sezioni, ma non fornisce giustificazioni sul perché Go abbia "nuovo" e "crea", invece di avere 1 alloc + generalizzata funzione.

5
Potrebbero essere combinati ed è stato persino proposto da Rob Pike ad un certo punto: groups.google.com/d/topic/golang-nuts/kWXYU95XN04/discussion . Alla fine non è passato attraverso motivi simili a quelli indicati nella tua risposta.
Evan Shaw,

12
L'andamento efficace indica che nuovo restituisce un valore azzerato, mentre la mappa assegna tipi, sezioni o canali non azzerati. Vedi golang.org/doc/effective_go.html#allocation_new
kristianp

Che dire m := map[string]int{}invece di m := make(map[string]int)? non è necessario preallocare anche le dimensioni.
Noam Manos,

165

Go ha diversi modi di allocazione della memoria e inizializzazione del valore:

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

L'allocazione può avvenire anche durante la creazione di letterali compositi.


newpuò essere utilizzato per allocare valori come numeri interi, &intè illegale:

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

La differenza tra newe makepuò essere vista guardando il seguente esempio:

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

Supponiamo che Go non abbia newe make, ma abbia la funzione integrata NEW. Quindi il codice di esempio sarebbe simile al seguente:

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

Il * sarebbe obbligatoria , così:

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

Sì, è possibile unire newe makein un'unica funzione integrata. Tuttavia, è probabile che una singola funzione incorporata crei più confusione tra i nuovi programmatori Go che avere due funzioni integrate.

Considerando tutti i punti precedenti, sembra più appropriato newe makerimanere separati.


@TorstenBronger Trovo che il nuovo sia più facile da leggere e mostra che questa è l'istanza in cui intè stato creato.
Daniel Toebe,

4
Intendevi scrivere make(Point)e make(int)in quelle ultime 2 righe?
Jimmy Huch,

27

makeLa funzione alloca e inizializza solo un oggetto di tipo slice, map o chan. Ad esempio new, il primo argomento è un tipo. Ma può anche prendere un secondo argomento, la dimensione. A differenza di new, il tipo restituito da make è uguale al tipo del suo argomento, non un puntatore ad esso. E il valore allocato viene inizializzato (non impostato su zero come nel nuovo). Il motivo è che slice, map e chan sono strutture di dati. Devono essere inizializzati, altrimenti non saranno utilizzabili. Questo è il motivo per cui new () e make () devono essere diversi.

I seguenti esempi di Effective Go lo rendono molto chiaro:

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), alloca solo la memoria per [] int, ma non si inizializza, quindi ritorna nil; non il puntatore alla memoria perché è inutilizzabile. make([]int)alloca e inizializza in modo che sia utilizzabile, quindi restituisce il suo indirizzo.
o0omycomputero0o

12
  • new(T)- Alloca memoria e la imposta sul valore zero per il tipo T .. ..
    che è 0per int , ""per stringa e nilper i tipi di riferimento ( slice , map , chan )

    Si noti che i tipi di riferimento sono solo puntatori ad alcune strutture di dati sottostanti , che non verranno creati dall'esempio new(T)
    : in caso di slice , l' array sottostante non verrà creato, quindi new([]int) restituisce un puntatore a nulla

  • make(T)- Alloca memoria per i tipi di dati di riferimento ( slice , map , chan ), inoltre inizializza le loro strutture di dati sottostanti

    Esempio: in caso di slice , l' array sottostante verrà creato con la lunghezza e la capacità specificate
    Tenere presente che, a differenza di C, un array è un tipo primitivo in Go!


Detto ciò:

  • make(T) si comporta come una sintassi composita-letterale
  • new(T)si comporta come var(quando la variabile non è inizializzata)

    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)
    }

    Esegui il programma

    -- 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

    Ulteriori letture:
    https://golang.org/doc/effective_go.html#allocation_new https://golang.org/doc/effective_go.html#allocation_make


  • le cose diventano più chiare con un esempio. votato :)
    Sumit Jha

    8

    Devi make()creare canali e mappe (e sezioni, ma anche quelli possono essere creati da array). Non esiste un modo alternativo per crearli, quindi non puoi rimuoverli make()dal tuo lessico.

    Per quanto riguarda new(), non conosco a fondo il motivo per cui ne hai bisogno quando puoi usare la sintassi di struct. Tuttavia ha un significato semantico unico, che è "creare e restituire una struttura con tutti i campi inizializzati al loro valore zero", che può essere utile.


    1
    Quindi nuovo dovrebbe essere evitato e preferisco solo l'uso della sintassi di Struct
    CommonSenseCode

    8

    A parte tutto ciò che è spiegato in Effective Go , la principale differenza tra new(T)e&T{} è che quest'ultimo esegue esplicitamente un'allocazione di heap. Tuttavia, va notato che ciò dipende dall'implementazione e quindi può essere soggetto a modifiche.

    Il confronto makecon newha poco senso poiché i due svolgono funzioni completamente diverse. Ma questo è spiegato in dettaglio nell'articolo collegato.


    10
    L'affermazione che &T{}esegue esplicitamente un'allocazione di heap è AFAIK non basata su nulla nelle specifiche. In realtà credo che l'analisi di escape stia già tenendo tale * T nello stack ogni volta che è possibile esattamente nello stesso modo di new(T).
    zzzz,

    6

    nuovo (T): restituisce un puntatore al tipo T un valore di tipo * T, alloca e azzera la memoria. nuovo (T) equivale a & T {} .

    make (T): restituisce un valore inizializzato di tipo T , alloca e inizializza la memoria. È usato per sezioni, mappe e canali.

    Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
    Licensed under cc by-sa 3.0 with attribution required.