Как кусок может содержать себя?

Я пытаюсь выучить Голанг, используя "Язык программирования Go", и я дошел до раздела, посвященного слайсам. Они сравнивают массивы и срезы тем, что два массива можно сравнить с == где два ломтика нельзя. Текст гласит следующее:

"== operator for arrays of strings, it may be puzzling that slice
comparisons do not also work this way. There are two reasons why deep 
equivalence is problematic. First, unlike array elements, the elements
of a slice are indirect, making it possible for a slice to contain 
itself. Although there are ways to deal with such cases, none is 
simple, efficient, and most importantly, obvious."

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

3 ответа

Ломтик, содержащий себя

Помимо рекурсивного типа (например, type Foo []Foo(см. ответ ANisus), который бесполезен, кроме демонстрации, срез может содержать сам себя, если, например, тип элемента среза interface{}:

s := []interface{}{"one", nil}
s[1] = s

В этом примере срез s будет иметь 2 значения интерфейса, первое "обёртывание" простой строкой "one"и другое значение интерфейса, охватывающее само значение среза. При создании значения интерфейса будет скопирована копия значения, которая в случае срезов означает копию заголовка / дескриптора среза, который содержит указатель на базовый массив, поэтому копия будет иметь то же значение указателя, указывающее на тот же базовый массив. (Для получения более подробной информации о представлении интерфейсов см . Законы отражения: Представление интерфейса.)

Если вы быстро напечатать это:

fmt.Println(s)

Вы получите фатальную ошибку, что-то вроде:

runtime: goroutine stack exceeds 250000000-byte limit
fatal error: stack overflow

Так как fmt.Println() пытается рекурсивно распечатать содержимое, и поскольку 2-й элемент является слайсом, указывающим на тот же массив печатаемого фрагмента, он попадает в бесконечный цикл.

Еще один способ узнать, действительно ли это сам фрагмент:

s := []interface{}{"one", nil}
s[1] = s
fmt.Println(s[0])

s2 := s[1].([]interface{})
fmt.Println(s2[0])

s3 := s2[1].([]interface{})
fmt.Println(s3[0])

Вывод (попробуйте на Go Playground):

one
one
one

Независимо от того, насколько глубоко мы углубимся, 2-й элемент всегда будет значением среза, указывающим на тот же массив, что и sзавернутый в interface{} значение.

Направление играет важную роль, поскольку копия будет помещена в interface{} но копия будет содержать тот же указатель.

Массив не может содержать себя

Изменение типа на массив:

s := [2]interface{}{"one", nil}
s[1] = s
fmt.Println(s[0])

s2 := s[1].([2]interface{})
fmt.Println(s2[0])

s3 := s2[1].([2]interface{})
fmt.Println(s3[0])

Вывод (попробуйте на Go Playground):

one
one
panic: interface conversion: interface is nil, not [2]interface {}

Это потому, что когда массив обернут в interface{}копия будет упакована - и копия не является исходным массивом. Так s будет иметь второе значение, interface{} оборачивание массива, но это другой массив, чье 2-е значение не установлено и поэтому будет nil (нулевое значение типа interface{}), поэтому попытка "войти" в этот массив вызовет панику, потому что nil ( утверждение типа не выполняется, потому что не использовалась специальная форма "запятая, хорошо").

Так как это s массив не содержит сам по себе, простой fmt.Println() покажет его полное содержание:

fmt.Println(s)

Выход:

[one [one <nil>]]

В дальнейшем interface{} анализ упаковки

Если вы оберните массив в interface{} и измените содержимое исходного массива, значение обернуто в interface{} не влияет:

arr := [2]int{1, 2}
var f interface{} = arr
arr[0] = 11

fmt.Println("Original array:    ", arr)
fmt.Println("Array in interface:", f)

Выход:

Original array:     [11 2]
Array in interface: [1 2]

Если вы сделаете то же самое со срезом, это повлияет и на обернутый срез (поскольку он указывает на один и тот же базовый массив):

s := []int{1, 2}
f = s
s[0] = 11

fmt.Println("Original slice:    ", s)
fmt.Println("Slice in interface:", f)

Выход:

Original slice:     [11 2]
Slice in interface: [11 2]

Попробуйте это на игровой площадке Go.

В приведенном ниже примере создается фрагмент, который содержит себя:

type Foo []Foo  
bar := make(Foo, 1)
bar[0] = bar

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

Массив, с другой стороны, является значением. В лучшем случае он может содержать указатели на себя.

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

typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

Я думаю, что это indirectпотому что на элементы ссылается указатель. и, конечно, мы можем иметь сам ломтик в void *data,

Другие вопросы по тегам