2016-07-28 5 views
18

Ich verstehe den Unterschied zwischen Arrays und Slices in Go. Aber was ich nicht verstehe ist, warum es hilfreich ist, Arrays überhaupt zu haben. Warum ist es hilfreich, dass eine Array-Typdefinition eine Länge und einen Elementtyp angibt? Warum kann nicht jedes "Array", das wir verwenden, eine Scheibe sein?Warum haben Arrays in Go?

+2

Ich glaube, ich habe ein mögliches Duplikat gefunden: [Warum Arrays anstelle von Slices verwenden?] (Http://StackOverflow.com/Questions/30694652/Why-use-Arrays-instead-of-Slices) – icza

Antwort

24

ist mehr als nur die arrays feste Länge: sie sind comparable, und sie sind Werte (nicht Referenz oder Zeigertypen).

Es gibt unzählige Vorteile von Arrays gegenüber Slices in bestimmten Situationen, die zusammen die Existenz von Arrays (zusammen mit Slices) mehr als rechtfertigen. Lass uns sie sehen. (Ich zähle nicht einmal Arrays die Bausteine ​​der Scheiben zu sein.)


1. Als vergleichbare Mittel Sie Arrays als Schlüssel in Karten verwenden können, aber nicht Scheiben schneiden. Ja, man könnte jetzt sagen, warum man dann Scheiben nicht vergleichbar macht, so dass dies allein die Existenz beider nicht rechtfertigen würde. Gleichheit ist in Slices nicht gut definiert. FAQ: Why don't maps allow slices as keys?

Sie implementieren keine Gleichheit, da die Gleichheit auf solchen Typen nicht gut definiert ist; Es gibt mehrere Überlegungen, die den Vergleich oberflächlicher und tiefer Vergleiche, den Vergleich zwischen Zeiger und Wert, den Umgang mit rekursiven Typen usw. betreffen.

2.Arrays können auch Sie geben höhere Compiler-Sicherheit, da die Indexgrenzen bei der Kompilierung geprüft werden können (Feldlänge zu einem nicht-negativen constant darstellbaren durch einen Wert vom Typ auswerten muß int):

s := make([]int, 3) 
s[3] = 3 // "Only" a runtime panic: runtime error: index out of range 

a := [3]int{} 
a[3] = 3 // Compile-time error: invalid array index 3 (out of bounds for 3-element array) 

3. auch , das um oder Zuweisen von Werten Array des gesamten Arrays implizit eine Kopie erstellen, wird es so sein, "losgelöst" vom ursprünglichen Wert. Wenn Sie ein Slice übergeben, erstellt es immer noch eine Kopie, aber nur des Slice Header, aber der Slice-Wert (der Header) wird auf das gleiche Backing-Array zeigen. Dies kann oder kann nicht sein, was Sie wollen. Wenn Sie ein Segment von dem "Original" trennen wollen, müssen Sie den Inhalt explizit kopieren, z. mit der eingebauten copy() Funktion zu einer neuen Scheibe.

a := [2]int{1, 2} 
b := a 
b[0] = 10 // This only affects b, a will remain {1, 2} 

sa := []int{1, 2} 
sb := sa 
sb[0] = 10 // Affects both sb and sa 

4. Auch da die Arraylänge Teil des Array-Typ ist, werden Arrays mit unterschiedlicher Länge unterschiedliche Typen. Auf der einen Seite kann dies ein "Schmerz im Arsch" sein (z.B.Sie schreiben eine Funktion, die einen Parameter vom Typ [4]int übernimmt, Sie können diese Funktion nicht verwenden, um ein Array vom Typ [5]int zu übernehmen und zu verarbeiten, aber dies kann auch ein Vorteil sein: hierzu kann die Länge explizit angeben das Array, das erwartet wird. Z.B. Wenn Sie eine Funktion schreiben möchten, die eine IPv4-Adresse verwendet, kann sie mit dem Typ [4]byte modelliert werden. Jetzt haben Sie eine Kompilierzeitgarantie, dass der an Ihre Funktion übergebene Wert genau 4 Bytes hat, nicht mehr und nicht weniger (was sowieso eine ungültige IPv4-Adresse wäre).

5. Bezogen auf die vorherigen, die Array-Länge kann auch eine Dokumentation Zweck dienen. Ein Typ [4]byte dokumentiert ordnungsgemäß, dass IPv4 4 Bytes hat. Eine rgb Variable vom Typ [3]byte teilt mit, dass es für jede Farbkomponente 1 Byte gibt. In einigen Fällen ist es sogar herausgenommen und steht zur Verfügung, separat dokumentiert; zum Beispiel im Paket crypto/md5: md5.Sum() gibt einen Wert vom Typ [Size]byte zurück, wobei md5.Size eine Konstante ist 16: die Länge einer MD5-Prüfsumme.

6. Sie sind auch sehr nützlich, wenn Planung Speicherlayout von Strukturtypen, JimB Antwort hier und this answer in greater detail and real-life example sehen.

7. Auch da Scheiben sind Kopf- und sie sind (fast) immer herumgereicht wie sie ist (ohne Zeiger), die Sprache spec ist restriktiver in Bezug auf Zeiger auf Scheiben als Zeiger auf Arrays. Zum Beispiel bietet die Spezifikation mehrere shorthands für den Betrieb mit Zeigern auf Arrays, während das gleiche gibt Kompilierungsfehler im Falle von Slices (weil es selten ist, Zeiger auf Slices zu verwenden, wenn Sie wollen/müssen, müssen Sie sein explizit über die Handhabung, lesen Sie mehr in this answer).

Solche Beispiele sind:

  • Slicing einen p Zeiger auf Array: p[low:high] eine Abkürzung für (*p)[low:high] ist. Wenn ein Zeiger auf Slice ist, ist dies der Kompilierungsfehler (spec: Slice expressions).

  • Indizierung eines Zeigers auf Array: p[i] ist eine Kurzschrift für (*p)[i]. Wenn Zeiger auf ein Segment ist, ist dies ein Fehler bei der Kompilierung (spec: Index expressions).

Beispiel:

pa := &[2]int{1, 2} 
fmt.Println(pa[1:1]) // OK 
fmt.Println(pa[1]) // OK 

ps := &[]int{3, 4} 
println(ps[1:1]) // Error: cannot slice ps (type *[]int) 
println(ps[1]) // Error: invalid operation: ps[1] (type *[]int does not support indexing) 

8.Zugriff (single) Array-Elemente ist effizienter als slice Elemente zuzugreifen; Wie bei Slices muss die Laufzeit eine implizite Zeigerdereferenz durchlaufen. Auch "die Ausdrücke len(s) und cap(s) sind Konstanten, wenn der Typ s ein Array oder Zeiger auf ein Array ist".

Mai werden überraschend, aber Sie können auch schreiben:

type IP [4]byte 

const x = len(IP{}) // x will be 4 

Es ist gültig und wird ausgewertet und kompilieren Zeit obwohl IP{} ist kein constant expression so z.B. const i = IP{} wäre ein Kompilierungsfehler! Danach, es ist nicht einmal überraschend, dass folgende Arbeiten auch:

const x2 = len((*IP)(nil)) // x2 will also be 4 

Hinweis: Wenn über einen vollständigen Array vs eine komplette Scheibe hin, da bei keinen Unterschied in der Leistung alles so offensichtlich sein, kann es optimiert werden, so dass Der Zeiger im Slice-Header wird nur einmal dereferenziert. Einzelheiten/Beispiel finden Sie unter Array vs Slice: accessing speed.


Siehe ähnliche Fragen, bei denen ein Array verwendet werden kann/mehr Sinn als eine Scheibe macht:

Why use arrays instead of slices?

Why can't Go slice be used as keys in Go maps pretty much the same way arrays can be used as keys?

Hash with key as an array type

Golang: How do I check the equality of three values elegantly?

Slicing a slice pointer passed as argument

Und das ist nur für Neugier:a slice can contain itself while an array can't. (Tatsächlich erleichtert diese Eigenschaft den Vergleich, da Sie nicht mit rekursiven Datenstrukturen arbeiten müssen).

must-read blogs:

Go Slices: usage and internals

Arrays, slices (and strings): The mechanics of 'append'

+0

IMHO sind diese nicht "grundlegende" Vorteile (d. H. Nicht spezifisch für schlechte Design-Entscheidungen in Go gemacht), außer "6" und "8". Ich glaube, Go-Designer wollten nur eine Möglichkeit für präzisere Speicherverwaltung hinterlassen, also haben wir Arrays. – gavv

0

Jedes Array könnte ein Segment sein, aber nicht jedes Segment könnte ein Array sein. Wenn Sie eine feste Sammlungsgröße haben, können Sie durch die Verwendung eines Arrays eine geringfügige Leistungsverbesserung erzielen. Zumindest speichern Sie den Platz, den der Slice-Header belegt.

2

Arrays sind effizienter platzsparend. Wenn Sie die Größe des Slice niemals aktualisieren (d. H. Mit einer vordefinierten Größe beginnen und niemals darüber hinausgehen), gibt es wirklich keinen großen Unterschied in der Performance. Aber es gibt zusätzlichen Overhead im Raum, da ein Slice einfach ein Wrapper ist, der das Array in seinem Kern enthält. Im Kontext verbessert es auch die Klarheit, da es den beabsichtigten Gebrauch der Variablen deutlicher macht.

+0

Dort kann tatsächlich ein sehr großer Leistungsunterschied sein. Die Verwendung von Zeigern (über eine Scheibe) kann die Datenlokalisierung beeinflussen und zusätzliche Kosten verursachen, um die Daten im Speicher nachzuschlagen. – JimB

3

Arrays sind Werte, und es ist oft nützlich, um einen Wert anstelle eines Zeigers zu haben.

Werte können verglichen werden, daher können Sie Arrays als Map-Schlüssel verwenden.

Werte werden immer initialisiert, also müssen Sie nicht initialisieren, oder make sie wie Sie mit einem Stück machen.

Arrays geben Ihnen ein bessere Kontrolle des Speicherlayouts, wo, wie Sie nicht Raum direkt in einer Struktur mit einer Scheibe zuordnen können, können Sie mit einem Array können:

type Foo struct { 
    buf [64]byte 
} 

Hier wird ein Foo Wert enthalten eine 64-Byte-Wert anstelle eines Slice-Headers, der separat initialisiert werden muss. Arrays werden auch verwendet, um Strukturen aufzufüllen, um die Ausrichtung bei der Interoperation mit C-Code zu erfüllen und um eine falsche gemeinsame Nutzung für eine bessere Cache-Leistung zu verhindern.

Ein weiterer Aspekt zur Verbesserung der Leistung ist, dass Sie das Speicherlayout besser definieren können als mit Slices, da die Datenlokalisierung einen großen Einfluss auf speicherintensive Berechnungen haben kann. Die Dereferenzierung eines Zeigers kann im Vergleich zu den Operationen, die an den Daten ausgeführt werden, viel Zeit in Anspruch nehmen, und das Kopieren von Werten, die kleiner als eine Cachespeicherzeile sind, verursacht nur geringe Kosten, so dass leistungskritischer Code aus diesem Grund häufig nur Arrays verwendet.