Ответы:
То, что вы можете сделать с make
этим, вы не можете сделать по-другому:
Это немного сложнее оправдать new
. Главное, что делает это проще - это создание указателей на несоставные типы. Две функции ниже эквивалентны. Просто немного более кратко:
func newInt1() *int { return new(int) }
func newInt2() *int {
var i int
return &i
}
m := map[string]int{}
вместо m := make(map[string]int)
? нет необходимости предварительно выделять размер.
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
остается отдельным.
int
он создается.
make(Point)
и make(int)
в этих последних двух строках?
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
new([]int)
, он просто выделяет память для [] int, но не инициализирует, поэтому он просто возвращает nil
; не указатель на память, потому что он непригоден для использования. make([]int)
выделяет и инициализирует, чтобы его можно было использовать, затем возвращает его адрес.
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.
Вам нужно make()
создавать каналы и карты (и фрагменты, но они также могут быть созданы из массивов). Там нет альтернативного способа сделать это, поэтому вы не можете удалитьmake()
из своего лексикона.
Что касается new()
, я не знаю ни одной причины, почему она вам нужна, когда вы можете использовать структурный синтаксис. Тем не менее, он имеет уникальное семантическое значение: «создать и вернуть структуру со всеми полями, инициализированными до их нулевого значения», что может быть полезно.
Помимо всего, что объясняется в Effective Go , основное различие между new(T)
и&T{}
заключается в том, что последний явно выполняет выделение кучи. Однако следует отметить, что это зависит от реализации и, следовательно, может быть изменено.
Сравнивать make
с не new
имеет смысла, поскольку эти два выполняют совершенно разные функции. Но это подробно объясняется в связанной статье.
&T{}
явно выполняет распределение кучи, является AFAIK, не основанным ни на чем в спецификациях. На самом деле, я считаю, что анализ escape уже хранит такие * T в стеке, когда это возможно, точно так же, как и с new(T)
.