Срез в пределах al oop кажется, сохраняет предыдущую / последнюю ссылку (в зависимости от длины среза) - PullRequest
1 голос
/ 14 июля 2020

Это кажется очень странным. В al oop есть локальная переменная slice с новым значением, присвоенным каждому l oop, и я добавляю этот фрагмент к глобальному sliceWrappers. После завершения l oop все значения внутри глобального среза содержат только ссылку на последнее значение, установленное в этой локальной переменной среза.

Код:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    var sliceWrappers [][]string
    initialSlice := append([]string{}, "hi")
    initialSlice = append(initialSlice, "there")
    
    // Remove this line and it works fine
    initialSlice = append(initialSlice, "I'm")
    
    for i := 0; i < 2; i++ {
        slice := append(initialSlice, strconv.Itoa(i))
        fmt.Printf("Slice Value : %+v, Initial Value : %+v\n", slice, initialSlice)
        sliceWrappers = append(sliceWrappers, slice)
    }

    for _, sliceWrapper := range sliceWrappers {
        fmt.Printf("%+v\n", sliceWrapper)
    }
}

Фактический результат:

Slice Value : [hi there I'm 0], Initial Value : [hi there I'm]
Slice Value : [hi there I'm 1], Initial Value : [hi there I'm]
[hi there I'm 1]
[hi there I'm 1]

Ожидаемый результат:

Slice Value : [hi there I'm 0], Initial Value : [hi there I'm]
Slice Value : [hi there I'm 1], Initial Value : [hi there I'm]
[hi there I'm 0]  <------ This is not happening
[hi there I'm 1]

Если я удаляю строку initialSlice = append(initialSlice, "I'm"), она работает отлично.

Slice Value : [hi there 0], Initial Value : [hi there]
Slice Value : [hi there 1], Initial Value : [hi there]
[hi there 0]  <------ Works Perfectly
[hi there 1]

Полагаю, это как-то связано с append

Встроенная функция append добавляет элементы в конец среза. Если он имеет достаточную емкость, место назначения повторно нарезается для размещения новых элементов.

Если за это отвечает вышеуказанное условие, то не должно быть значение initialSlice, которое было напечатано внутри l oop также должно быть таким же, как slice?

Детская площадка - https://play.golang.org/p/b3SDGoA2Lzv

PS: К сожалению, я написал тестовые примеры для мой код всего с 3 уровнями вложенности, и он прошел нормально. Теперь мне нужно позаботиться о копировании для срезов внутри петель.

Ответы [ 2 ]

1 голос
/ 14 июля 2020
// Remove this line and it works fine
//initialSlice = append(initialSlice, "I'm")
fmt.Printf("Slice Value : %p - %d\n", initialSlice, cap(initialSlice))
...
for i := 0; i < 2; i++ {
    slice := append(initialSlice, strconv.Itoa(i))
    fmt.Printf("Slice Value : %p - %p\n", slice, initialSlice)
    ...
}

распечатайте address и capacity из initialSlice и slice , как указано выше. при раскомментировании добавляется строка . Он выводит, как показано ниже:

Slice Value : 0xc00009c000 - 2
Slice Value : 0xc0000a6000 - 0xc00009c000
Slice Value : 0xc00009e040 - 0xc00009c000

, и если прокомментировать строку, выведите ниже:

Slice Value : 0xc00009e000 - 4
Slice Value : 0xc00009e000 - 0xc00009e000
Slice Value : 0xc00009e000 - 0xc00009e000

И затем почему он выводится так, как вы ожидаете, когда комментируете строку?
, потому что в этой сцене емкость initialSlice равна 2 . Новый базовый массив будет выделен, когда добавляются новые элементы, поскольку для него недостаточно места для выполнения действия добавления.
И когда вы раскомментируете строку, емкость initialSlice будет 4, он изменит массив на месте.

ссылка: append do c

Встроенная функция append добавляет элементы в конец ломтик. Если он имеет достаточную емкость, место назначения перенаправляется для размещения новых элементов. Если этого не произойдет, будет выделен новый базовый массив. Добавить возвращает обновленный фрагмент. Поэтому необходимо сохранять результат добавления, часто в переменной, содержащей сам срез: slice = append (slice, elem1, elem2) slice = append (slice, anotherSlice ...) В особом случае разрешено добавить строку к байтовому фрагменту, например: slice = append ([] byte ("привет"), "world" ...)

1 голос
/ 14 июля 2020

Срезы основаны на указателе на массив, длине и емкости. Вы помещаете slice в sliceWrappers на первой итерации (чтобы он содержал указатель на массив). На второй итерации вызов append(initialSlice, strconv.Itoa(i)) изменяет значения в этом же массиве, потому что расположение памяти не изменилось. На этот массив указывает slice в первой и второй итерации, поэтому оба фрагмента, которые заканчиваются в sliceWrappers, указывают на одни и те же данные.

Этого можно избежать, скопировав данные в новый slice, прежде чем добавлять его в sliceWrappers:

    for i := 0; i < 2; i++ {
        slice := append(initialSlice, strconv.Itoa(i))
        fmt.Printf("Slice Value : %+v, Initial Value : %+v\n", slice, initialSlice)
        copiedSlice := make([]string, len(slice))
        copy(copiedSlice, slice)
        sliceWrappers = append(sliceWrappers, copiedSlice)
    }

Что дает ожидаемый результат:

Slice Value : [hi there I'm 0], Initial Value : [hi there I'm]
Slice Value : [hi there I'm 1], Initial Value : [hi there I'm]
[hi there I'm 0]
[hi there I'm 1]

Что касается удаления строки initialSlice = append(initialSlice, "I'm"): когда вы добавляете к срезу, он проверит, может ли он соответствовать новой длине внутри емкости. Если нет, он выделит новый массив (и, следовательно, новую ячейку памяти). Более короткий фрагмент, содержащий "привет там", находится на своей емкости, и добавление к нему выделит новый массив и создаст фрагмент с большей емкостью.

  • Если у вас есть строка initialSlice = append(initialSlice, "I'm") в вашей программе , новый массив будет размещен перед l oop. append(...) внутри l oop не вызовет нового выделения.
  • Если у вас нет строки в вашей программе, append(...) внутри l oop вызовет новое распределение, поэтому каждый из них получает отдельную ячейку памяти, поэтому они не перезаписывают друг друга.

Мой источник: Go Срезы: использование и внутреннее устройство https://blog.golang.org/slices-intro#TOC_4.

...