Risposte:
Le cose che puoi fare makeche non puoi fare in nessun altro modo:
È 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
}
m := map[string]int{}invece di m := make(map[string]int)? non è necessario preallocare anche le dimensioni.
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.
intè stato creato.
make(Point)e make(int)in quelle ultime 2 righe?
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
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.
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
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.
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.
&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).