web-dev-qa-db-fra.com

tranches nulles vs tranches non nuls vs tranches vides en langue Go

Je suis une programmation newbee to go. J'ai lu dans le livre de programmation aller que cette tranche se compose de trois choses: un pointeur sur un tableau, la longueur et la capacité.

Je suis confus entre tranches nulles (la tranche n'a pas de tableau sous-jacent, len = 0, cap = 0), des tranches non nuls où seul len = 0, cap = 0 et des tranches vides.

Quelqu'un peut-il dire s'il vous plaît si les tranches nulles et vides sont identiques? Si les deux sont différents, alors s'il vous plaît dites quelle est la différence entre ces deux?

Comment tester si une tranche est vide ou non?

En outre, quelle est la valeur du pointeur dans les tranches non nulles, dont la longueur et la capacité sont égales à zéro?

15
Surbhi Vyas

Comportement observable

nil et les tranches vides (avec une capacité de 0) ne sont pas identiques, mais leur comportement observable est identique. Je veux dire par là:

  • Vous pouvez les transmettre aux commandes len() et cap()
  • Vous pouvez for range dessus (ce sera 0 itérations)
  • Vous pouvez les découper (en respectant les restrictions décrites dans Spec: expressions Slice ; le résultat sera donc également une tranche vide).
  • Étant donné que leur longueur est égale à 0, vous ne pouvez pas modifier leur contenu (l'ajout d'une valeur crée une nouvelle valeur de tranche)

Voir cet exemple simple (une tranche nil et 2 tranches vides non -nil):

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 {}

Sortie (essayez-le sur Go Playground ):

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

(Notez que le découpage d'une tranche nil donne une tranche nil, tandis que le découpage d'une tranche non -nil donne une tranche non -nil.)

Vous ne pouvez faire la différence qu'en comparant la valeur de la tranche à l'identificateur prédéclaré nil, ils se comportent de la même manière dans tous les autres aspects.

Pour savoir si une tranche est vide, comparez simplement sa longueur à 0: len(s) == 0. Peu importe qu'il s'agisse de la tranche nil ou d'une tranche non -nil, il importe également si sa capacité est positive; s'il n'y a pas d'éléments, c'est vide.

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

Imprime (essayez sur le Go Playground ):

Empty: true , but capacity: 100

Sous la capuche

Une valeur de tranche est représentée par une structure définie dans reflect.SliceHeader :

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

Dans le cas d'une tranche nil, cette structure aura sa valeur zéro, c'est-à-dire que tous ses champs auront leur valeur zéro, c'est-à-dire: 0.

Avoir une tranche non -nil avec à la fois une capacité et une longueur égales aux champs 0, Len et Cap sera très certainement 0, mais le pointeur Data pourrait ne pas l'être. Ce n'est pas {sera}, c'est ce qui le différencie de la tranche nil. Cela pointera vers un tableau sous-jacent de taille zéro.

Notez que la spécification Go permet aux valeurs de différents types ayant une taille égale à 0 d'avoir la même adresse mémoire. Spécifications: Considérations relatives au système: Dimensions et garanties d'alignement:

Un type de structure ou de tableau a la taille zéro s'il ne contient aucun champ (ou élément, respectivement) de taille supérieure à zéro. Deux variables de taille zéro distinctes peuvent avoir la même adresse en mémoire.

Vérifions cela. Pour cela, nous appelons l'aide de unsafe package et "obtenons" la structure reflect.SliceHeader "vue" de nos valeurs de tranche:

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)))

Sortie (essayez-le sur 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}

Que voit-on?

  • Toutes les tranches (en-têtes de tranche) ont des adresses de mémoire différentes
  • La tranche nil a le pointeur de données 0
  • Les tranches s2 et s3 ont le même pointeur de données, partageant/pointant vers la même valeur de mémoire de taille 0 
27
icza

Une tranche peut être nulle dans un sens très littéral:

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

C'est le seul cas facile. La notion de tranche "vide" n'est pas bien définie: une tranche s avec len(s) == 0 est certainement vide, quelle que soit sa capacité . La chose la plus sensée à faire est la suivante: Ignorer l'implémentation sous-jacente, on n'a jamais besoin de savoir comment une tranche est représenté en interne. Tout ce qui compte est le comportement défini d'une tranche.

Comment tester si une tranche est vide ou non?

La définition la plus sensée de "slice s is empty" est une tranche ne contenant aucun élément et se traduisant par len(s) == 0. Cette définition est valable pour les tranches nuls et non nuls.

Quelqu'un peut-il dire s'il vous plaît si les tranches nulles et vides sont identiques? Si les deux sont différents, alors s'il vous plaît dites quelle est la différence entre ces deux?

Techniquement, une tranche nulle et une tranche non nulle sont différentes (l'une étant == nulle, l'autre étant! = Nulle), mais cette distinction n'a généralement pas d'importance, car vous pouvez append passer d'une valeur nulle à une valeur nulle et inversement. 0

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

Lisez à propos de zéro dans Go pour plus d’informations. Une tranche nulle est comme un canal nul ou une carte nulle: elle est non initialisée. Vous initialisez soit par makeing, soit par un littéral. Comme indiqué ci-dessus, il n'y a aucune raison de penser à la représentation sous-jacente.

En outre, quelle est la valeur du pointeur dans les tranches non nulles, dont la longueur et la capacité sont nulles?

Il s'agit d'un détail d'implémentation qui peut varier d'un compilateur à l'autre ou même d'une version à l'autre. Personne n'a besoin de le savoir pour écrire des programmes Go corrects et portables.

1
Volker