¿Por qué haría () o new ()?

203

Los documentos de introducción dedican muchos párrafos a explicar la diferencia entre new()y make(), pero en la práctica, puede crear objetos dentro del ámbito local y devolverlos.

¿Por qué usarías el par de asignadores?

slezica
fuente

Respuestas:

170

Cosas que puedes hacer con las makeque no puedes hacer de otra manera:

  • Crea un canal
  • Crear un mapa con espacio preasignado
  • Cree un segmento con espacio preasignado o con len! = Cap

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
}
Evan Shaw
fuente
41
Es cierto que 'nuevo' no se puede utilizar para crear canales. Sin embargo, en mi opinión, el punto es: ¿qué pasaría si 'nuevo' y 'hacer' se unen en una sola función incorporada? Ciertamente, tal reemplazo sería posible. Como es posible, la pregunta es: ¿cuáles son las razones objetivas para tener 2 funciones integradas en lugar de solo 1 función integrada generalizada? - Su respuesta dice correctamente que 'nuevo' no se puede usar para crear canales / mapas / sectores, pero no proporciona justificaciones de por qué Go tiene 'nuevo' y 'hacer', en lugar de tener 1 función generalizada alloc + init.
55
Podrían combinarse e incluso Rob Pike lo propuso en un momento: groups.google.com/d/topic/golang-nuts/kWXYU95XN04/discussion . Finalmente, no sucedió por razones similares a las que se dan en su respuesta.
Evan Shaw
12
Go eficaz indica que new devuelve un valor de cero, mientras que map asigna tipos, zero o channel de tipos no cero. Ver golang.org/doc/effective_go.html#allocation_new
kristianp
¿Qué pasa en m := map[string]int{}lugar de m := make(map[string]int)? No es necesario preasignar el tamaño también.
Noam Manos
165

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.


newpuede usarse para asignar valores como enteros, &intes 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 newy makese 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 newy 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 newy makeformar 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 newy makepermanecer separado.


fuente
@TorstenBronger Me parece nuevo para ser más fácil de leer y muestra que esa es la instancia donde intse crea.
Daniel Toebe
44
¿Querías escribir make(Point)y make(int)en esas 2 últimas líneas?
Jimmy Huch
27

makeLa 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
Sinatra
fuente
1
En 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.
o0omycomputero0o
12
  • new(T)- Asigna memoria y la establece en el valor cero para el tipo T ..
    ..esto es 0para int , ""para string y nilpara 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

  • Loris
    fuente
    las cosas se vuelven más claras con un ejemplo. upvoted :)
    Sumit Jha
    8

    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.

    Lily Ballard
    fuente
    1
    Por lo tanto, debe evitarse lo nuevo y simplemente preferir el uso de la sintaxis de Struct
    CommonSenseCode
    8

    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 makea newtiene mucho sentido ya que los dos realizan funciones completamente diferentes. Pero esto se explica en detalle en el artículo vinculado.

    jimt
    fuente
    10
    La afirmación que &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).
    zzzz
    6

    nuevo (T): devuelve un puntero para escribir T un valor de tipo * T, asigna y pone a cero la memoria. nuevo (T) es equivalente a & T {} .

    make (T): devuelve un valor inicializado de tipo T , asigna e inicializa la memoria. Se utiliza para sectores, mapas y canales.

    M.Nair
    fuente