2013-07-11 4 views
9

Ich mag die Tatsache, dass Go mir nicht eine Million Möglichkeiten gibt, einfache Dinge zu tun - aus The Zen von Python zu leihen, "Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben um es zu tun. "Bevorzugter Weg, Typen zu initiieren Go

Allerdings bin ich nicht klar auf der bevorzugten/idiomatischen Art der Instanziierung von Typen. Die Basistypen sind einfach:

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

Aber was ist mit Strukturen? Entspricht das Folgende, und wenn ja, welches wird bevorzugt und warum?

var f Foo  
f := Foo{} 

Was ist mit Scheiben? Ich kann var xs []int, xs := []int{} oder xs := make([]int) tun, aber ich denke, die erste Option (im Gegensatz zu mit Strukturen) unterscheidet sich von den anderen? Ich nehme an, dies gilt auch für Karten.

Mit Zeigern höre ich, dass new vermieden werden sollte. Ist das ein guter Rat, und wenn ja, was würde als gültige Verwendung von new gelten?

Ich weiß, dass dies teilweise eine Frage des Stils sein kann, aber eine Begründung für die Bevorzugung eines bestimmten Stils wäre in jedem Fall hilfreich.

Antwort

7

Wenn Sie eine Variable deklarieren, wo T ist irgendeine Art:

var name T 

Gehen Sie ein Stück nicht initialisierten gibt „genullt“ Gedächtnis.

Mit Primitiven bedeutet dies, var name int wäre 0, und var name string wäre "". In C it might be zeroed, or might be something unexpected. Go garantiert, dass eine nicht initialisierte Variable das Null-Äquivalent des Typs ist.

Interne Slices, Karten und Kanäle werden als Zeiger behandelt. Zeiger Nullwert ist Null, was bedeutet, dass er auf Null Speicher zeigt. Ohne es zu initialisieren, können Sie eine Panik bekommen, wenn Sie versuchen, darauf zu operieren.

Die Funktion make wurde speziell für einen Schnitt, eine Karte oder einen Kanal entwickelt. Die Argumente der Schliesser sind:

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? 

A Scheiben length ist, wie viele Elemente es beginnt mit. Die Kapazität ist der zugewiesene Speicher, bevor eine Größenänderung erforderlich ist (intern, neue Größe * 2, dann kopieren). Weitere Informationen finden Sie unter Effective Go: Allocation with make.

Struktur: new(T) entspricht &T{}, nicht T{}. *new(T) entspricht *&T{}.

Scheiben: make([]T,0) entspricht []T{}.

Karten: make(map[T]T) entspricht map[T]T{}.

Soweit die Methode bevorzugt wird, frage ich mich, die folgende Frage:

Kenne ich den Wert (e) jetzt in der Funktion?

Wenn die Antwort "Ja" ist, dann gehe ich mit einem der oben genannten T{...}. Wenn die Antwort "Nein" ist, verwende ich make oder new.

Zum Beispiel würde ich so etwas wie dies vermeiden:

type Name struct { 
    FirstName string 
    LastName string 
} 

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

Stattdessen würde ich so etwas tun:

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

Warum? Denn mit new(Name) mache ich klar, dass ich beabsichtige, um die Werte später zu füllen. Wenn ich verwenden würde, wäre es nicht klar, dass ich beabsichtigte, einen Wert später in derselben Funktion hinzuzufügen/zu ändern, ohne den Rest des Codes zu lesen.

Die Ausnahme ist mit Strukturen, wenn Sie keinen Zeiger wollen. Ich werde T{} verwenden, aber ich werde nichts hinzufügen, wenn ich die Werte hinzufügen/ändern möchte. Natürlich funktioniert auch *new(T), aber das ist wie mit *&T{}. T{} ist sauberer in diesem Fall, obwohl ich dazu neige, Zeiger mit Strukturen zu verwenden, um eine Kopie zu vermeiden, wenn ich sie herumließe.

Eine andere Sache zu beachten, eine []*struct ist kleiner und billiger zu resize als []struct, unter der Annahme, die Struktur ist viel größer als ein Zeiger, der in der Regel 4 - 8 Bytes ist (8 Bytes auf 64bit?).

4

Sie können sich die Quellen der Go-Standardbibliothek ansehen, in denen Sie viele idiomatische Go-Codes finden.

Sie haben recht: var xs []int unterscheidet sich von den anderen beiden Varianten, da es xs nicht "initialisiert", xs ist null. Während die anderen zwei wirklich eine Scheibe konstruieren. xs := []int{} ist üblich, wenn Sie eine leere Scheibe mit Nullkappe benötigen, während make Ihnen mehr Optionen bietet: Länge und Kapazität. Auf der anderen Seite ist es üblich, mit einer Nullschicht zu beginnen und durch Anfügen wie in var s []int; for ... { s = append(s, num) } zu füllen.

new kann nicht total vermieden werden, da es die einzige Möglichkeit ist, einen Zeiger z. zu uint32 oder den anderen eingebauten Typen. Aber Sie haben Recht, schreiben a := new(A) ist ziemlich ungewöhnlich und geschrieben, meist als a := &A{}, da dies in a := &A{n: 17, whatever: "foo"} verwandelt werden kann. Die Verwendung von new ist nicht wirklich entmutigt, aber angesichts der Fähigkeit von Strukturliteralen sieht es einfach wie ein Rest von Java für mich aus.

4

Während des Fireside-Chats mit dem Go-Team bei Google IO fragte jemand im Publikum das Go-Team, was sie aus der Sprache entfernen möchten.

Rob sagte, er wünschte, es gab weniger Art und Weise Variablen zu deklarieren und erwähnt:

Colon gleich für überschreiben, mit dem Namen Ergebnisparameter (https://plus.google.com/+AndrewGerrand/posts/LmnDfgehorU), variable in einer wiederverwendet für Schleife verwirrend, vor allem für Verschlüsse. Allerdings wird sich die Sprache wahrscheinlich nicht viel ändern.

+0

+1 Beantwortet die Frage nicht wirklich, aber ich bin mir sicher - ich bin froh zu wissen, dass Pike es selbst gesagt hat. Das ist eine Schwäche, die ich bei GoLang finde: Zu viele Möglichkeiten, die Vor- und Nachteile und die Angemessenheit von ihnen zu deklarieren und nicht zu klären - das gibt mir manchmal eine Art "nicht ganz fertig". – Vector