Respostas:
Coisas que você pode fazer e makeque não pode fazer de outra maneira:
É um pouco mais difícil de justificar new. A principal coisa que facilita é criar ponteiros para tipos não compostos. As duas funções abaixo são equivalentes. Um é apenas um pouco mais conciso:
func newInt1() *int { return new(int) }
func newInt2() *int {
var i int
return &i
}
m := map[string]int{}vez de m := make(map[string]int)? não é necessário pré-alocar o tamanho também.
O Go possui várias maneiras de alocação de memória e inicialização de valor:
&T{...}, &someLocalVar, new,make
A alocação também pode ocorrer ao criar literais compostos.
newpode ser usado para alocar valores como números inteiros, &inté 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
A diferença entre newe makepode ser vista observando o seguinte exemplo:
p := new(chan int) // p has type: *chan int
c := make(chan int) // c has type: chan int
Suponha que o Go não tenha newe make, mas tenha a função interna NEW. Em seguida, o código de exemplo ficaria assim:
p := NEW(*chan int) // * is mandatory
c := NEW(chan int)
O * seria obrigatório , então:
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
Sim, fundindo newe makeem uma única função built-in é possível. No entanto, é provável que uma única função interna leve a mais confusão entre os novos programadores Go do que ter duas funções internas.
Considerando todos os pontos acima, parece mais apropriado newe makepermanecer separado.
inté criado.
make(Point)e make(int)nessas duas últimas linhas?
makeA função aloca e inicializa um objeto do tipo fatia, mapa ou chan apenas. Como new, o primeiro argumento é um tipo. Mas, também pode levar um segundo argumento, o tamanho. Diferente do novo, o tipo de retorno do make é o mesmo que o tipo de argumento, não um ponteiro para ele. E o valor alocado é inicializado (não definido como valor zero como no novo). A razão é que fatia, mapa e chan são estruturas de dados. Eles precisam ser inicializados, caso contrário, não serão utilizáveis. Esta é a razão pela qual new () e make () precisam ser diferentes.
Os exemplos a seguir do Effective Go deixam muito 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), ele apenas aloca memória para [] int, mas não inicializa, apenas retorna nil; não o ponteiro para a memória porque é inutilizável. make([]int)aloca e inicializa para que seja utilizável; em seguida, retorne seu endereço.
new(T)- Aloca memória e define o valor zero para o tipo T ... que
é 0para int , ""para string e nilpara tipos referenciados ( fatia , mapa , chan )
Observe que os tipos referenciados são apenas ponteiros para algumas estruturas de dados subjacentes , que não serão criadas pelo new(T)
Exemplo: no caso de fatia , a matriz subjacente não será criada, new([]int)
retornando assim um ponteiro a nada
make(T)- Aloca memória para tipos de dados referenciados ( fatia , mapa , chan ), além de inicializar suas estruturas de dados subjacentes
Exemplo: no caso de fatia , a matriz subjacente será criada com o comprimento e a capacidade especificados.
Lembre-se de que, diferentemente de C, uma matriz é um tipo primitivo no Go!
Dito isto:
make(T) comporta-se como sintaxe literal composta
new(T)se comporta como var(quando a variável não é inicializada)
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)
}
Execute o 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
Leitura adicional:
https://golang.org/doc/effective_go.html#allocation_new
https://golang.org/doc/effective_go.html#allocation_make
Você precisa make()criar canais e mapas (e fatias, mas eles também podem ser criados a partir de matrizes). Não há maneira alternativa de fazer isso, então você não pode remover make()do seu léxico.
Quanto a new(), não conheço nenhuma razão imediata por que você precisa quando pode usar a sintaxe struct. Porém, ele possui um significado semântico exclusivo, que é "criar e retornar uma estrutura com todos os campos inicializados com seu valor zero", o que pode ser útil.
Além de tudo explicado no Effective Go , a principal diferença entre new(T)e &T{}é que o último executa explicitamente uma alocação de heap. No entanto, deve-se notar que isso depende da implementação e, portanto, pode estar sujeito a alterações.
Comparando makecom newfaz pouco sentido, pois os dois desempenham funções totalmente diferentes. Mas isso é explicado em detalhes no artigo vinculado.
&T{}executa explicitamente uma alocação de heap é AFAIK, não baseada em nada nas especificações. Na verdade, acredito que a análise de escape já esteja mantendo esse * T na pilha sempre que possível, exatamente da mesma maneira que comnew(T) .