Odpowiedzi:
Rzeczy, które możesz z make
tym zrobić , nie możesz zrobić w żaden inny sposób:
To trochę trudniejsze do uzasadnienia new
. Najważniejsze, co ułatwia, to tworzenie wskaźników do typów niekompozytowych. Dwie poniższe funkcje są równoważne. Jest tylko trochę bardziej zwięzłe:
func newInt1() *int { return new(int) }
func newInt2() *int {
var i int
return &i
}
m := map[string]int{}
zamiast m := make(map[string]int)
? nie ma też potrzeby wstępnego przydzielania rozmiaru.
Go ma wiele sposobów alokacji pamięci i inicjowania wartości:
&T{...}
, &someLocalVar
, new
,make
Alokacja może również nastąpić podczas tworzenia literałów kompozytowych.
new
może być użyty do przydzielenia wartości takich jak liczby całkowite, &int
jest nielegalny:
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
Różnicę między new
i make
można zobaczyć, patrząc na następujący przykład:
p := new(chan int) // p has type: *chan int
c := make(chan int) // c has type: chan int
Załóżmy, że Go nie ma new
i make
ma wbudowaną funkcję NEW
. Wówczas przykładowy kod wyglądałby tak:
p := NEW(*chan int) // * is mandatory
c := NEW(chan int)
*
Byłoby obowiązkowe , więc:
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
Tak, łączenie new
i make
w jednej wbudowanej funkcji jest możliwe. Jest jednak prawdopodobne, że pojedyncza wbudowana funkcja doprowadziłaby do większego zamieszania wśród nowych programistów Go niż posiadanie dwóch wbudowanych funkcji.
Biorąc pod uwagę wszystkie powyższe punkty, wydaje się bardziej odpowiednie new
i make
pozostać oddzielne.
int
został utworzony.
make(Point)
i make(int)
w tych ostatnich 2 linie?
make
funkcja przydziela i inicjuje tylko obiekt typu plasterek, mapa lub chan. Podobnie new
jak pierwszy argument jest typem. Ale może również wymagać drugiego argumentu, rozmiaru. W przeciwieństwie do new, zwracany typ make jest taki sam jak typ argumentu, a nie wskaźnik do niego. Przydzielona wartość jest inicjalizowana (nie jest ustawiona na zero, jak w nowym). Powodem jest to, że plasterek, mapa i chan to struktury danych. Muszą zostać zainicjowane, w przeciwnym razie nie będą przydatne. To jest powód, dla którego new () i make () muszą być inne.
Poniższe przykłady z Effective Go wyjaśniają:
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)
po prostu przydziela pamięć dla [] int, ale nie inicjuje, więc po prostu zwraca nil
; nie wskaźnik do pamięci, ponieważ jest bezużyteczny. make([]int)
przydziela i inicjuje, aby można było z niego skorzystać, a następnie zwrócił adres.
new(T)
- Przydziela pamięć i ustawia ją na wartość zerową dla typu T .. ..
to jest 0
dla int , ""
dla łańcucha i nil
dla typów odniesienia ( plasterek , mapa , chan )
Zauważ, że przywoływane typy są tylko wskaźnikami do niektórych podstawowych struktur danych , które nie zostaną utworzone przez new(T)
Przykład: w przypadku wycinka , podstawowa tablica nie zostanie utworzona, w ten sposób new([]int)
zwraca wskaźnik do zera
make(T)
- Przydziela pamięć dla odnośnych typów danych ( plasterek , mapa , chan ), a także inicjuje ich podstawowe struktury danych
Przykład: w przypadku wycinka podstawowa tablica zostanie utworzona z określoną długością i pojemnością
Pamiętaj, że w przeciwieństwie do C, tablica jest prymitywnym typem w Go!
Biorąc to pod uwagę:
make(T)
zachowuje się jak składnia literałowo-złożona
new(T)
zachowuje się jak var
(gdy zmienna nie jest zainicjowana)
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)
}
Uruchom program
-- 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
Dalsza lektura:
https://golang.org/doc/effective_go.html#allocation_new
https://golang.org/doc/effective_go.html#allocation_make
Musisz make()
tworzyć kanały i mapy (i wycinki, ale można je również tworzyć z tablic). Nie ma alternatywnego sposobu na ich wykonanie, więc nie można usunąć make()
z leksykonu.
Co do new()
tego, nie znam żadnego bezpośredniego powodu, dla którego potrzebujesz go, kiedy możesz użyć składni struct. Ma jednak unikalne znaczenie semantyczne: „utwórz i zwróć strukturę ze wszystkimi polami zainicjowanymi do wartości zerowej”, co może być przydatne.
Oprócz wszystkiego wyjaśnionego w Effective Go , główna różnica między new(T)
i &T{}
polega na tym, że ta ostatnia jawnie wykonuje przydział sterty. Należy jednak zauważyć, że jest to zależne od wdrożenia i dlatego może ulec zmianie.
Porównywanie make
z nie new
ma większego sensu, ponieważ te dwie funkcje pełnią zupełnie różne funkcje. Ale wyjaśniono to szczegółowo w powiązanym artykule.
&T{}
jawnie wykonuje alokację sterty, jest AFAIK nieoparte na niczym w specyfikacji. Właściwie uważam, analiza ucieczka już utrzymanie takiego * t na stosie w miarę możliwości w dokładnie taki sam sposób jak z new(T)
.