2013-07-11 57 views
9

Podoba mi się fakt, że Go nie daje mi miliona sposobów robienia prostych rzeczy - pożyczenia od Zen Pythona, "Powinien istnieć jeden - a najlepiej tylko jeden - oczywisty sposób aby to zrobić. "Preferowany sposób tworzenia instancji w Go

Nie jestem jednak przekonany co do preferowanego/idiomatycznego sposobu tworzenia instancji. Podstawowe typy są proste:

n := 0 
t := 1.5 
str := "Hello" 

Ale co ze strukturami? Czy jest następujący odpowiednik, a jeśli tak, jaki jest preferowany i dlaczego?

var f Foo  
f := Foo{} 

Co z plastrami? Mogę zrobić var xs []int, xs := []int{} lub xs := make([]int), ale myślę, że pierwsza opcja (w przeciwieństwie do z structs) różni się od innych? Zakładam, że będzie to miało również zastosowanie do map.

Ze wskazówkami, słyszę, że należy unikać stosowania new. Czy to dobra rada, a jeśli tak, to, co można by uznać za prawidłowe użycie new?

Zdaję sobie sprawę, że częściowo może to być kwestia stylu, ale w każdym przypadku pomocne byłoby preferowanie określonego stylu.

Odpowiedz

7

Kiedy deklarujesz zmienną, gdzie T jest jakiś rodzaj:

var name T 

Go daje kawałek niezainicjowany „wyzerowany” pamięć.

W przypadku prymitywów oznacza to, że var name int będzie miało wartość 0, a var name string będzie "". W C it might be zeroed, or might be something unexpected. Go gwarantuje, że niezainicjowana zmienna jest zerowym odpowiednikiem tego typu.

Wewnętrzne plasterki, mapy i kanały są traktowane jako wskaźniki. Wskaźniki zero mają wartość zerową, co oznacza, że ​​wskazuje zerową pamięć. Bez inicjalizacji możesz napotkać panikę, jeśli spróbujesz na niej działać.

Funkcja make jest specjalnie zaprojektowana dla plasterka, mapy lub kanału. argumenty aby funkcjonowała są:

make(T type, length int[, capacity int]) // For slices. 
make(T[, capacity int]) // For a map. 
make(T[, bufferSize int]) // For a channel. How many items can you take without blocking? 

plasterki length to ile przedmiotów zaczyna się. Pojemność to przydzielona pamięć, zanim konieczna będzie zmiana rozmiaru (wewnętrznie, nowy rozmiar * 2, a następnie skopiuj). Aby uzyskać więcej informacji, zobacz Effective Go: Allocation with make.

Struktury: new(T) jest odpowiednikiem &T{}, a nie T{}. *new(T) jest odpowiednikiem *&T{}.

Plastry: make([]T,0) jest odpowiednikiem []T{}.

Mapy: make(map[T]T) jest odpowiednikiem map[T]T{}.

miarę, która jest preferowaną metodą, zadaję sobie następujące pytanie:

Czy znam wartość (y) w tej chwili wewnątrz funkcji?

Jeśli odpowiedź brzmi "tak", to używam jednego z powyższych T{...}. Jeśli odpowiedź brzmi "nie", to używam make lub new.

Na przykład, chciałbym uniknąć coś takiego:

type Name struct { 
    FirstName string 
    LastName string 
} 

func main() { 
    name := &Name{FirstName:"John"} 
    // other code... 
    name.LastName = "Doe" 
} 

Zamiast tego chciałbym zrobić coś takiego:

func main() { 
    name := new(Name) 
    name.FirstName = "John" 
    // other code... 
    name.LastName = "Doe" 
} 

Dlaczego? Ponieważ, używając new(Name), wyraźnie zaznaczam, że I zamierza wypełnić później wartości. Gdybym użył , nie byłoby jasne, że zamierzałem dodać/zmienić wartość później w tej samej funkcji bez czytania reszty kodu.

Wyjątkiem jest struktura, gdy nie chcesz wskaźnika. Będę używał T{}, ale niczego nie dodam, jeśli chcę dodać/zmienić wartości. Oczywiście działa również *new(T), ale to tak, jakby używać *&T{}. T{} jest w tym przypadku czystszy, chociaż zwykle używam wskaźników ze strukturami, aby uniknąć kopiowania podczas przekazywania go.

Jeszcze jedna rzecz, o której należy pamiętać: []*struct jest mniejszy i tańszy w zmianie rozmiaru niż []struct, zakładając, że struktura jest znacznie większa niż wskaźnik, który zwykle ma 4 - 8 bajtów (8 bajtów na 64-bitowym?).

4

Można zajrzeć do źródeł biblioteki standardowej Go, gdzie można znaleźć wiele idiomatycznych kodów Go.

Masz rację: var xs []int różni się od pozostałych dwóch wariantów, ponieważ nie "inicjalizuje" xs, xs jest zerowe. Podczas gdy pozostali dwaj naprawdę tworzą kawałek. xs := []int{} jest powszechny, jeśli potrzebujesz pustego plasterka z zerową nakładką, a make oferuje więcej opcji: długość i pojemność. Z drugiej strony często zaczyna się od pustego plasterka i wypełnia się, dodając jako var s []int; for ... { s = append(s, num) }.

new nie można całkowicie uniknąć, ponieważ jest to jedyny sposób na utworzenie wskaźnika, np. do uint32 lub innych typów wbudowanych. Ale masz rację, pisanie a := new(A) jest dość niecodzienne i napisane głównie jako a := &A{}, ponieważ można to zmienić w a := &A{n: 17, whatever: "foo"}. Użycie numeru new nie jest odradzane, ale biorąc pod uwagę zdolność literałów strukturalnych, po prostu wygląda na to, że pozostało mi z Javy.

4

Podczas wizyty w sieci Fireside z zespołem Go w Google IO, ktoś z publiczności zapytał zespół Go, co chcieliby zrobić z tego języka.

Rob powiedział, że chciał tam było mniej sposób deklarowania zmiennych i wymienić:

Colon równa dla nadpisać nazwanych parametrów wynikowych (https://plus.google.com/+AndrewGerrand/posts/LmnDfgehorU), zmienna ponownie użyte w pętli for jest mylące, zwłaszcza dla zamknięć. Jednak język prawdopodobnie niewiele się zmieni.

+0

+1 Nie odpowiada na pytanie, ale na pewno się zgadzam - cieszę się, że tak powiedział Pike. Jest to jedna słabość, którą odkrywam w GoLangu: zbyt wiele sposobów deklarowania i nieczytania korzyści i wad oraz ich stosowności - daje mi czasem poczucie "niezupełnie skończone". – Vector