it-swarm.com.de

null Scheiben gegen Nicht-Null-Scheiben gegen leere Scheiben in Go-Sprache

Ich bin ein Neuling, um zu programmieren. Ich habe im go-Programmierbuch gelesen, dass Slice aus drei Dingen besteht: einem Zeiger auf ein Array, Länge und Kapazität.

Ich werde verwirrt zwischen null Slices (Slice hat kein darunter liegendes Array, auf das gezeigt werden soll, len = 0, cap = 0), nicht-null Slices, wobei nur len = 0, cap = 0 und leere Slices sind.

Kann jemand bitte sagen, ob null und leere Scheiben gleich sind? Wenn beide unterschiedlich sind, dann sagen Sie bitte, was ist der Unterschied zwischen diesen beiden?

Wie kann man testen, ob ein Slice leer ist oder nicht?

Welchen Wert hat der Zeiger auch in Nicht-Null-Schnitten, deren Länge und Kapazität Null sind?

15
Surbhi Vyas

Beobachtbares Verhalten

nil und leere Slices (mit 0 Kapazität) sind nicht dasselbe, aber ihr beobachtbares Verhalten ist dasselbe. Damit meine ich:

  • Sie können sie an die eingebauten Funktionen len() und cap() übergeben
  • Sie können for range Über sie (wird 0 Iterationen sein)
  • Sie können sie aufteilen (indem Sie nicht gegen die unter Spec: Slice expressions angegebenen Einschränkungen verstoßen; das Ergebnis ist also auch ein leeres Stück).
  • Da ihre Länge 0 ist, können Sie ihren Inhalt nicht ändern (das Anhängen eines Werts erstellt einen neuen Slice-Wert).

Sehen Sie sich dieses einfache Beispiel an (ein nil - Slice und 2 nicht - nil leere Slices):

var s1 []int         // nil slice
s2 := []int{}        // non-nil, empty slice
s3 := make([]int, 0) // non-nil, empty slice

fmt.Println("s1", len(s1), cap(s1), s1 == nil, s1[:], s1[:] == nil)
fmt.Println("s2", len(s2), cap(s2), s2 == nil, s2[:], s2[:] == nil)
fmt.Println("s3", len(s3), cap(s3), s3 == nil, s3[:], s3[:] == nil)

for range s1 {}
for range s2 {}
for range s3 {}

Ausgabe (versuchen Sie es auf dem Go Playground ):

s1 0 0 true [] true
s2 0 0 false [] false
s3 0 0 false [] false

(Beachten Sie, dass das Schneiden eines Slice nil zu einem Slice nil und das Schneiden eines Slice ohne nil zu einem Slice ohne nil führt.)

Sie können den Unterschied nur feststellen, indem Sie den Slice-Wert mit dem vordeklarierten Bezeichner nil vergleichen. Sie verhalten sich in allen anderen Aspekten gleich.

Um festzustellen, ob ein Slice leer ist, vergleichen Sie einfach seine Länge mit 0: len(s) == 0. Es spielt keine Rolle, ob es sich um den Slice nil oder einen Slice ohne nil handelt. Es spielt auch keine Rolle, ob er eine positive Kapazität hat. Wenn es keine Elemente enthält, ist es leer.

s := make([]int, 0, 100)
fmt.Println("Empty:", len(s) == 0, ", but capacity:", cap(s))

Ausdrucke (versuchen Sie es auf dem Go Playground ):

Empty: true , but capacity: 100

Unter der Haube

Ein Slice-Wert wird durch eine Struktur dargestellt, die in reflect.SliceHeader definiert ist:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

Im Fall eines nil - Slice hat diese Struktur den Wert Null, dh alle ihre Felder haben den Wert Null, dh 0.

Wenn ein Nicht-Slice mit einer Kapazität und Länge von nil gleich 0, Len und Cap ist, ist dies mit Sicherheit 0. aber der Data Zeiger kann nicht sein. Es wird nicht sein, das ist es, was es von der Scheibe nil unterscheidet. Es zeigt auf ein zugrunde liegendes Array der Größe Null.

Beachten Sie, dass die Go-Spezifikation Werte verschiedener Typen mit der Größe 0 für dieselbe Speicheradresse zulässt. Spezifikation: Systemüberlegungen: Garantie für Größe und Ausrichtung:

Ein Struktur- oder Array-Typ hat die Größe Null, wenn er keine Felder (oder Elemente) enthält, deren Größe größer als Null ist. Zwei unterschiedliche Variablen mit der Größe Null haben möglicherweise dieselbe Adresse im Speicher.

Lassen Sie uns das überprüfen. Dazu rufen wir die Hilfe des Pakets unsafe auf und "holen" uns die Strukturansicht reflect.SliceHeader Unserer Slice-Werte:

var s1 []int
s2 := []int{}
s3 := make([]int, 0)

fmt.Printf("s1 (addr: %p): %+8v\n",
    &s1, *(*reflect.SliceHeader)(unsafe.Pointer(&s1)))
fmt.Printf("s2 (addr: %p): %+8v\n",
    &s2, *(*reflect.SliceHeader)(unsafe.Pointer(&s2)))
fmt.Printf("s3 (addr: %p): %+8v\n",
    &s3, *(*reflect.SliceHeader)(unsafe.Pointer(&s3)))

Ausgabe (versuchen Sie es auf dem Go Playground ):

s1 (addr: 0x1040a130): {Data:       0 Len:       0 Cap:       0}
s2 (addr: 0x1040a140): {Data: 1535812 Len:       0 Cap:       0}
s3 (addr: 0x1040a150): {Data: 1535812 Len:       0 Cap:       0}

Was sehen wir?

  • Alle Slices (Slice-Header) haben unterschiedliche Speicheradressen
  • Das Slice nil hat einen Datenzeiger 0
  • Die Slices s2 Und s3 Haben denselben Datenzeiger, der auf denselben Speicherwert der Größe 0 verweist
33
icza

Ein Slice kann im wahrsten Sinne des Wortes Null sein:

var n []int
n == nil // true

Das ist der einzig einfache Fall. Der Begriff "leere" Slice ist nicht klar definiert: Eine Slice s mit len(s) == 0 ist auf jeden Fall leer, unabhängig von ihrer Kapazität. Das Vernünftigste ist, die zugrunde liegende Implementierung zu ignorieren ist intern vertreten. Alles was zählt ist das definierte Verhalten eines Slice.

Wie kann man testen, ob ein Slice leer ist oder nicht?

Die sinnvollste Definition von "Slice s ist leer" ist ein Slice, das keine Elemente enthält, die in len(s) == 0 übersetzt werden. Diese Definition gilt sowohl für Null als auch für Nicht-Null-Scheiben.

Kann jemand bitte sagen, ob null und leere Scheiben gleich sind? Wenn beide unterschiedlich sind, dann sagen Sie bitte, was ist der Unterschied zwischen diesen beiden?

Technisch gesehen sind eine Null-Scheibe und eine Nicht-Null-Scheibe unterschiedlich (eine ist == Null, die andere ist! = Null), aber diese Unterscheidung spielt normalerweise keine Rolle, da Sie append eine Null-Scheibe und keine Null-Schicht zurücklegen können 0

var n []int
len(n) == cap(n) == 0 // true
n = append(n, 123)
len(n) == 1 // true

Weitere Informationen finden Sie in Go über die Nullwerte. Ein Nullteil ist wie ein Nullkanal oder eine Nullkarte: Es ist nicht initialisiert. Sie initialisieren sich entweder durch makeing oder durch ein Literal. Wie oben erwähnt, gibt es keinen Grund, über die zugrunde liegende Darstellung nachzudenken.

Welchen Wert hat der Zeiger auch in Nicht-Null-Schnitten, deren Länge und Kapazität Null sind?

Dies ist ein Implementierungsdetail, das von Compiler zu Compiler oder sogar von Version zu Version variieren kann. Niemand muss dies wissen, um korrekte und tragbare Go-Programme zu schreiben.

1
Volker