Копирование параметра среза структуры в Go

Допустим, у меня есть структура с параметром среза, как показано ниже, и я создаю ее и заполняю ее некоторыми значениями:

type s struct {
  sl []float32
}

func NewS() *s {
  return &s{
    sl: make([]float32, 3),
  }
}

func main() {
  a := NewS()
  a.sl[0] = 1
  a.sl[1] = 2
  a.sl[2] = 3

  b := NewS()
  // Code here
}

Я хочу, чтобы b имел sl с теми же значениями, что и a.sl, и, в частности, я хотел бы понять, как это сделать разными способами:

  1. Где весь b указывает на весь a (чтобы, если бы в структуре были другие параметры, все они совпадали бы)
  2. Где b.sl будет указывать на a.sl
  3. Как глубокая копия, где нет указателей (и выполняется не путем итерации по срезу, а таким образом, который в идеале повторяется независимо от типа)

Далее следует то, что я пробовал и нашел до сих пор.

b = a
fmt.Printf("a: %p\n", &a)
fmt.Printf("b: %p\n", &b)
fmt.Printf("a.sl: %p\n", &a.sl)
fmt.Printf("b.sl: %p\n", &b.sl)

Который выводит разные адреса памяти для структур, но одни и те же адреса памяти для срезов. Почему это? Почему бы и адреса структур не быть одинаковыми, если a просто указывает на b?

a: 0xc00000e030
b: 0xc00000e038
a.sl: 0xc000054040
b.sl: 0xc000054040

Затем я попробовал:

b.sl = a.sl
// ...

Который выводит все разные адреса памяти. Понятно, что адреса a и b разные, но почему b.sl не указывает на адрес a.sl?

a: 0xc00000e028
b: 0xc00000e030
a.sl: 0xc00000c030
b.sl: 0xc00000c048

И, наконец, я попробовал:

copy(b.sl, a.sl)
// ...

Что выводит аналогично приведенному выше. Это единственный результат, который я ожидал, так как я ожидал, что это сделает глубокую копию.

a: 0xc0000b8018
b: 0xc0000b8020
a.sl: 0xc0000ae018
b.sl: 0xc0000ae030

Не могли бы вы помочь мне понять, что происходит в этих трех случаях, и другие способы достижения желаемых результатов и выполнения различных типов копий?


person Sam    schedule 26.03.2021    source источник


Ответы (2)


Вот некоторая справочная информация: Срез содержит указатель на резервный массив слайса, длину среза и емкость резервного массива. (Указатель, длина, емкость) называется заголовком фрагмента.

Адрес слайса — это адрес заголовка слайса, а не указатель на резервный массив слайса или первый элемент резервного массива слайса.

б = а

Переменные a и b указывают на одно и то же значение после выполнения инструкции.

Переменные a и b различны и поэтому имеют разные адреса.

Выражение &a.sl — это адрес поля sl в указанном значении. Выражение &b.sl равно &a.sl, потому что a и b указывают на одно и то же значение.

Это ваш номер 1: a и b баллов с одинаковым значением.

b.sl = a.sl

Оператор копирует заголовок слайса из a.sl в b.sl. Слайсы имеют разные адреса, потому что заголовки слайсов остаются разными.

Это близко к вашему # 2: a.sl точка b.sl в том же резервном массиве. Изменение элемента в a.sl видно через b.sl, но изменения в заголовке слайса a.sl не отражаются в `b.sl.

копировать(b.sl, a.sl)

Оператор копирует элементы резервного массива a.sls в b.sl.

Это ваш номер 3, глубокая копия. Изменения до a не отражаются в b.

person thwd    schedule 26.03.2021

Срез — это тройка, содержащая указатель на базовый массив, длину и емкость. Когда вы назначаете слайс другому слайсу, вы назначаете эту тройку. Базовый массив остается прежним.

Срез — это представление массива. Если вам нужна глубокая копия слайса, вам нужно создать еще один слайс и скопировать его самостоятельно.

Если вы назначаете массив другому, это копирует каждый элемент источника в цель.

person Burak Serdar    schedule 26.03.2021