Respuestas:
Cosas que puedes hacer con las make
que no puedes hacer de otra manera:
Es un poco más difícil de justificar new
. Lo principal que facilita es crear punteros a tipos no compuestos. Las dos funciones siguientes son equivalentes. Uno es un poco más conciso:
func newInt1() *int { return new(int) }
func newInt2() *int {
var i int
return &i
}
m := map[string]int{}
lugar de m := make(map[string]int)
? No es necesario preasignar el tamaño también.
Go tiene múltiples formas de asignación de memoria e inicialización de valor:
&T{...}
, &someLocalVar
, new
,make
La asignación también puede ocurrir al crear literales compuestos.
new
puede usarse para asignar valores como enteros, &int
es ilegal:
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 diferencia entre new
y make
se puede ver mirando el siguiente ejemplo:
p := new(chan int) // p has type: *chan int
c := make(chan int) // c has type: chan int
Supongamos que Go no tiene new
y make
, pero tiene la función incorporada NEW
. Entonces el código de ejemplo se vería así:
p := NEW(*chan int) // * is mandatory
c := NEW(chan int)
El *
sería obligatorio , entonces:
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í, es posible fusionar new
y make
formar una sola función integrada. Sin embargo, es probable que una sola función integrada genere más confusión entre los nuevos programadores de Go que tener dos funciones integradas.
Teniendo en cuenta todos los puntos anteriores, parece más apropiado new
y make
permanecer separado.
int
se crea.
make(Point)
y make(int)
en esas 2 últimas líneas?
make
La función asigna e inicializa un objeto de tipo sector, mapa o chan únicamente. Como new
, el primer argumento es un tipo. Pero, también puede tomar un segundo argumento, el tamaño. A diferencia del nuevo, el tipo de retorno de make es el mismo que el tipo de su argumento, no un puntero al mismo. Y el valor asignado se inicializa (no se establece en valor cero como en new). La razón es que el corte, el mapa y el chan son estructuras de datos. Deben inicializarse, de lo contrario no serán utilizables. Esta es la razón por la que new () y make () deben ser diferentes.
Los siguientes ejemplos de Effective Go lo dejan muy claro:
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)
, solo asigna memoria para [] int, pero no se inicializa, por lo que solo regresa nil
; no es el puntero a la memoria porque es inutilizable. make([]int)
asigna e inicializa para que sea utilizable, luego devuelva su dirección.
new(T)
- Asigna memoria y la establece en el valor cero para el tipo T ..
..esto es 0
para int , ""
para string y nil
para los tipos referenciados ( slice , map , chan )
Tenga en cuenta que los tipos referenciados son solo punteros a algunas estructuras de datos subyacentes , que no se crearán con el new(T)
Ejemplo: en caso de división , la matriz subyacente no se creará, por lo tanto, no new([]int)
devuelve un puntero a nada
make(T)
- Asigna memoria para los tipos de datos referenciados ( corte , mapa , chan ), además de inicializar sus estructuras de datos subyacentes
Ejemplo: en caso de corte , la matriz subyacente se creará con la longitud y capacidad especificadas.
Tenga en cuenta que, a diferencia de C, una matriz es un tipo primitivo en Go!
Habiendo dicho eso:
make(T)
se comporta como sintaxis literal compuesta
new(T)
se comporta como var
(cuando la variable no se inicializa)
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)
}
Ejecuta el programa
-- 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
Más información:
https://golang.org/doc/effective_go.html#allocation_new
https://golang.org/doc/effective_go.html#allocation_make
Necesita make()
crear canales y mapas (y segmentos, pero también se pueden crear a partir de matrices). No hay una forma alternativa de hacerlos, por lo que no puede eliminarlos make()
de su léxico.
En cuanto a new()
, no sé de ninguna razón por qué lo necesitas cuando puedes usar la sintaxis de estructura. Sin embargo, tiene un significado semántico único, que es "crear y devolver una estructura con todos los campos inicializados a su valor cero", lo que puede ser útil.
Además de todo lo explicado en Effective Go , la principal diferencia entre new(T)
y &T{}
es que este último realiza explícitamente una asignación de montón. Sin embargo, debe tenerse en cuenta que esto depende de la implementación y, por lo tanto, puede estar sujeto a cambios.
Comparando make
a new
tiene mucho sentido ya que los dos realizan funciones completamente diferentes. Pero esto se explica en detalle en el artículo vinculado.
&T{}
realiza explícitamente una asignación de montón es AFAIK no basada en nada en las especificaciones. En realidad, creo que el análisis de escape ya mantiene tal * T en la pila siempre que sea posible de la misma manera que con new(T)
.