Зачем мне делать () или новый ()?

203

Во вводных документах много абзацев, посвященных объяснению различий между new()и make(), но на практике вы можете создавать объекты в локальной области и возвращать их.

Зачем вам использовать пару распределителей?

slezica
источник

Ответы:

170

То, что вы можете сделать с makeэтим, вы не можете сделать по-другому:

  • Создать канал
  • Создать карту с заранее выделенным пространством
  • Создайте фрагмент с заранее выделенным пространством или с len! = Cap

Это немного сложнее оправдать new. Главное, что делает это проще - это создание указателей на несоставные типы. Две функции ниже эквивалентны. Просто немного более кратко:

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

func newInt2() *int {
    var i int
    return &i
}
Эван Шоу
источник
41
Это правда, что «новое» не может быть использовано для создания каналов. Однако, на мой взгляд, суть в том, что произойдет, если 'new' и 'make' объединятся в одну встроенную функцию? Конечно, такая замена была бы возможна. Поскольку это возможно, вопрос заключается в следующем: каковы объективные причины наличия 2 встроенных функций, а не 1 обобщенной встроенной функции. - В вашем ответе правильно сказано, что «new» нельзя использовать для создания каналов / карт / слайсов, но он не дает обоснования того, почему Go имеет «new» и «make» вместо 1 обобщенной функции alloc + init.
5
Их можно объединить, и это было даже предложено Робом Пайком в одном месте: groups.google.com/d/topic/golang-nuts/kWXYU95XN04/discussion . В конечном итоге это не произошло по причинам, аналогичным приведенным в вашем ответе.
Эван Шоу
12
Эффективный переход указывает на то, что new возвращает нулевое значение, тогда как map выделяет ненулевые типы карты, среза или канала. См. Golang.org/doc/effective_go.html#allocation_new
kristianp
А что m := map[string]int{}вместо m := make(map[string]int)? нет необходимости предварительно выделять размер.
Ноам Манос
165

Go имеет несколько способов выделения памяти и инициализации значения:

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

Выделение также может происходить при создании составных литералов.


newможет использоваться для выделения значений, таких как целые числа, &intнедопустимо:

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

Разницу между newи makeможно увидеть, посмотрев на следующий пример:

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

Предположим, что Go не имеет newи make, но имеет встроенную функцию NEW. Тогда пример кода будет выглядеть так:

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

Это * будет обязательно , так что:

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

Да, слияние newиmake в единую встроенную функцию. Тем не менее, вполне вероятно, что одна встроенная функция приведет к большей путанице среди новых программистов Go, чем наличие двух встроенных функций.

Учитывая все вышеперечисленные моменты, представляется более уместным newи makeостается отдельным.


источник
@TorstenBronger Я считаю, что новое легче читать, и показывает, что это тот случай, когда intон создается.
Дэниел Тобе,
4
Ты хотел написать make(Point)и make(int)в этих последних двух строках?
Джимми Хуч
27

makeФункция выделяет и инициализирует объект типа slice, map или chan only. Мол new, первый аргумент - это тип. Но он также может принимать второй аргумент, размер. В отличие от new, возвращаемый тип make совпадает с типом аргумента, а не с указателем на него. И выделенное значение инициализируется (не устанавливается в нулевое значение, как в новом). Причина в том, что слайс, карта и канал являются структурами данных. Их нужно инициализировать, иначе их нельзя будет использовать.Это причина, по которой new () и make () должны отличаться.

Следующие примеры из Effective Go проясняют это:

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
источник
1
В new([]int), он просто выделяет память для [] int, но не инициализирует, поэтому он просто возвращает nil; не указатель на память, потому что он непригоден для использования. make([]int)выделяет и инициализирует, чтобы его можно было использовать, затем возвращает его адрес.
o0omycomputero0o
12
  • new(T)- Распределяет память и устанавливает ее в нулевое значение для типа T ..
    ..это 0для int , ""для строки и nilдля ссылочных типов ( slice , map , chan )

    Обратите внимание, что ссылочные типы - это просто указатели на некоторые базовые структуры данных , которые не будут созданы в new(T)
    примере: в случае слайса базовый массив не будет создан, поэтому он new([]int) возвращает указатель на ничто

  • make(T)- выделяет память для ссылочных типов данных ( срез , карта , канал ), а также инициализирует их базовые структуры данных

    Пример: в случае среза базовый массив будет создан с заданной длиной и емкостью.
    Помните, что, в отличие от C, массив является примитивным типом в Go!


Что, как говорится:

  • make(T) ведет себя как составной-буквальный синтаксис
  • new(T)ведет себя как var(когда переменная не инициализирована)

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

    Запустите программу

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

    Дополнительная информация:
    https://golang.org/doc/effective_go.html#allocation_new. https://golang.org/doc/effective_go.html#allocation_make.

  • Лорис
    источник
    все становится более понятным на примере. проголосовал :)
    Sumit Jha
    8

    Вам нужно make()создавать каналы и карты (и фрагменты, но они также могут быть созданы из массивов). Там нет альтернативного способа сделать это, поэтому вы не можете удалитьmake() из своего лексикона.

    Что касается new(), я не знаю ни одной причины, почему она вам нужна, когда вы можете использовать структурный синтаксис. Тем не менее, он имеет уникальное семантическое значение: «создать и вернуть структуру со всеми полями, инициализированными до их нулевого значения», что может быть полезно.

    Лили Баллард
    источник
    1
    Так что нового следует избегать и просто отдавать предпочтение использованию синтаксиса
    Struct
    8

    Помимо всего, что объясняется в Effective Go , основное различие между new(T)и&T{} заключается в том, что последний явно выполняет выделение кучи. Однако следует отметить, что это зависит от реализации и, следовательно, может быть изменено.

    Сравнивать makeс не newимеет смысла, поскольку эти два выполняют совершенно разные функции. Но это подробно объясняется в связанной статье.

    jimt
    источник
    10
    Утверждение, что &T{}явно выполняет распределение кучи, является AFAIK, не основанным ни на чем в спецификациях. На самом деле, я считаю, что анализ escape уже хранит такие * T в стеке, когда это возможно, точно так же, как и с new(T).
    zzzz
    6

    new (T): он возвращает указатель на тип T значение типа * T, он выделяет и обнуляет память. new (T) эквивалентно & T {} .

    make (T): возвращает инициализированное значение типа T , выделяет и инициализирует память. Его используют для срезов, карты и каналов.

    M.Nair
    источник