Dlaczego miałbym tworzyć () lub nowy ()?

203

Dokumenty Wprowadzenie poświęcić wiele akapitów do wyjaśnienia różnicy między new()i make(), ale w praktyce, można tworzyć obiekty w zakresie lokalnym i ich zwrotu.

Dlaczego miałbyś używać pary alokatorów?

Slezica
źródło

Odpowiedzi:

170

Rzeczy, które możesz z maketym zrobić , nie możesz zrobić w żaden inny sposób:

  • Utwórz kanał
  • Utwórz mapę z wstępnie przydzielonym miejscem
  • Utwórz plasterek z wstępnie przydzielonym miejscem lub za pomocą len! = Cap

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
}
Evan Shaw
źródło
41
Prawdą jest, że „nowego” nie można używać do tworzenia kanałów. Jednak moim zdaniem chodzi o to, co by się stało, gdyby „nowy” i „make” zostały połączone w jedną wbudowaną funkcję? Z pewnością taka wymiana byłaby możliwa. Ponieważ jest to możliwe, pytanie brzmi: jakie są obiektywne powody posiadania 2 wbudowanych funkcji, a nie tylko 1 ogólnej wbudowanej funkcji. - Twoja odpowiedź poprawnie mówi, że „nowy” nie może być użyty do tworzenia kanałów / map / plasterków, ale nie zawiera uzasadnienia, dlaczego Go ma „nowe” i „make”, zamiast mieć 1 uogólnioną funkcję przydzielania + inicjowania.
5
Można je połączyć, a Rob Pike zaproponował to w pewnym momencie: groups.google.com/d/topic/golang-nuts/kWXYU95XN04/discussion . Ostatecznie nie udało się to z powodów podobnych do podanych w odpowiedzi.
Evan Shaw,
12
Skuteczne przejście powoduje, że nowy zwraca wartość zerową, podczas gdy mapa przydziela typy niezerowane mapę, plasterek lub kanał. Zobacz golang.org/doc/effective_go.html#allocation_new
kristianp
Co powiesz na m := map[string]int{}zamiast m := make(map[string]int)? nie ma też potrzeby wstępnego przydzielania rozmiaru.
Noam Manos
165

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.


newmoże być użyty do przydzielenia wartości takich jak liczby całkowite, &intjest 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 newi makemoż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 newi makema 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 newi makew 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 newi makepozostać oddzielne.


źródło
@ TorstenBronger Uważam, że nowy jest łatwiejszy do odczytania i pokazuje, że jest to instancja, w której intzostał utworzony.
Daniel Toebe
4
Czy chodziło Ci napisać make(Point)i make(int)w tych ostatnich 2 linie?
Jimmy Huch
27

makefunkcja przydziela i inicjuje tylko obiekt typu plasterek, mapa lub chan. Podobnie newjak 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
Sinatra
źródło
1
W 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.
o0omycomputero0o
12
  • new(T)- Przydziela pamięć i ustawia ją na wartość zerową dla typu T .. ..
    to jest 0dla int , ""dla łańcucha i nildla 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

  • Loris
    źródło
    wszystko staje się bardziej jasne na przykładzie. upvoted :)
    Sumit Jha
    8

    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.

    Lily Ballard
    źródło
    1
    Dlatego należy unikać nowych i po prostu preferować stosowanie składni Struct
    CommonSenseCode,
    8

    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 makez nie newma większego sensu, ponieważ te dwie funkcje pełnią zupełnie różne funkcje. Ale wyjaśniono to szczegółowo w powiązanym artykule.

    jimt
    źródło
    10
    Oświadczenie, które &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).
    zzzz
    6

    new (T): zwraca wskaźnik, aby wpisać T wartość typu * T, przydziela i zeruje pamięć. nowy (T) jest równoważny z & T {} .

    make (T): zwraca zainicjowaną wartość typu T , przydziela i inicjuje pamięć. Jest używany do plasterków, mapy i kanałów.

    M.Nair
    źródło