Суть среза в языке Go — SliceHeader

Go
Суть среза в языке Go — SliceHeader

Самое горячее сегодня это выпуск WeChat 7.0, который добавляет короткие видео и оптимизирует такие функции, как просмотр.Давайте поговорим о языке Go (golang).Похоже, что если вы хотите стать квалифицированным селф-медиа, вам не следует ограничиваться , Есть долгий путь.

Два дня назад друг (Вилин) оставил сообщение на моем официальном аккаунте, и статья, оставившая сообщение, - вот эта.Боевые заметки на языке Go (5) | Go slice, это очень ранняя статья про язык Go (golang) Slice (слайс). Сообщение этого друга не о его собственной проблеме, а объяснение сообщения другого друга (Dreamerque).

Причина оставить сообщение

Чтобы связно объяснить проблему, давайте сначала посмотрим на сообщение друга Dreamerque от 17 марта 2018 года:

Существует проблема: Рассмотрите возможность использования среза ссылочного типа в качестве пользовательского приемника и привяжите метод следующим образом:

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

type Slice []int

func (A Slice)Append(value int) {
	A = append(A, value)
}

func main() {
	mSlice := make(Slice, 10, 20)
	mSlice.Append(5)
	fmt.Println(mSlice)
}

Через код, я думаю, каждый может понять, что выше проблема и путаница Dreamerque. Мой ответ Dreamerque в то время заключался в том, что приведенные источники данных несовместимы, и я попросил его сослаться на мойРазница между new и make в языке GoЭта статья .

Затем всего два дня назад я получил сообщение от Weelin:

Привет безжалостный, я понимаю, что источник данных mslice не должен меняться. Из-за копирования значения единственное, что связано со срезами до и после метода Append, — это массив, на который указывает базовый слой.Результат печати отличается, потому что исходный срез слишком короткий. Это также может создать новый фрагмент (длиной больше 5) и распечатать подтверждение после выполнения метода Append.

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

анализ проблемы

Из приведенного выше вывода мы действительно можем видетьmSliceНичего не изменилось, только методAppendНичего не сделал. Путаница Dreamerque заключается в том, что Slice является ссылочным типом, и его также следует изменить, если он модифицируется.На самом деле мы знаем, что измененная ссылка находится вAppendВ методе уход не работает.

На самом деле ничего из вышеперечисленного не является принципиальным, это то, о чем вообще упоминал Вилин,appendПоследний Slice больше не является оригинальным Slice. В это время некоторые друзья могут снова запутаться.appendУказатель возвращенного среза совпадает с указателем исходного среза, как он может быть другим? Давайте проверим это один раз, изменив код следующим образом:

func (A Slice)Append(value int) {
	A1 := append(A, value)
	fmt.Printf("%p\n%p\n",A,A1)
}

мы используемA1место храненияappendSlice, возвращенный методом, затем распечатайте результатA1ВахараAАдрес указателя оказывается точно таким же. Вы можете попробовать это сами. На самом деле мыmakeПри использовании слайса обнаружится, что он может иметь три параметра, один — данные, другой — длина, а третий — емкость, то есть слайс — это такая структура, и теперь она должно быть нашимSliceHeaderВремя появиться.

Дебют SliceHeader

SliceHeader — это конкретная производительность среды выполнения Slice, и его структура определяется следующим образом:

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

Это соответствует трем элементам Slice,DataУказывает на конкретный базовый массив источников данных,Lenобозначает длину,Capпредставляет емкость.

Поскольку Slice — это SliceHeader, давайте преобразуем Slice в SliceHeader, посмотримAа такжеA1Внутренние специальные значения полей, поэтому, чтобы судить о том, согласуются ли они, мы модифицируемAppendМетоды, как показано ниже:

//blog:www.flysnow.org
//wechat:flysnow_org

func (A Slice)Append(value int) {
	A1 := append(A, value)

	sh:=(*reflect.SliceHeader)(unsafe.Pointer(&A))
	fmt.Printf("A Data:%d,Len:%d,Cap:%d\n",sh.Data,sh.Len,sh.Cap)

	sh1:=(*reflect.SliceHeader)(unsafe.Pointer(&A1))
	fmt.Printf("A1 Data:%d,Len:%d,Cap:%d\n",sh1.Data,sh1.Len,sh1.Cap)
}

пройти черезunsafe.PointerСтрелки отлиты, оunsafe.Pointerзнание может относиться к моемуБоевые заметки о языке Go (27) | Go unsafe PointerЭта статья.

преобразуются в*reflect.SliceHeaderПосле типа мы выводим их соответствующиеData,Len,Capполе, теперь мы смотрим на результат вывода.

A  Data:824634204160,Len:10,Cap:20
A1 Data:824634204160,Len:11,Cap:20

Теперь все понимают, что ихLenНе то же самое, не Slice, так что используйтеappendметод не изменяет оригиналA, но недавно созданныйA1, даже если друг Dreamerque передаст следующий кодA = append(A, value)сделать копию, тоже простоmSliceкопияAменяется, и этоAтолько вAppendэффективен в рамках метода,mSliceсам по себе не изменился, поэтому выводmSliceНичего не изменить.

Здесь правильно сделать, чтобыAppendвернутьappendрезультат после. На самом деле, для встроенных функцийappendИспользование языка Go (golang) официально объяснено, и возвращаемое значение следует сохранить.

Append returns the updated slice. It is therefore necessary to store the result of append

В приведенном выше примере друга Dreamerque Len установлен на 10, а Cap — на 20. Поскольку Cap достаточно велик, встроенная функцияappendНовый базовый массив не создается, теперь мы меняем Cap на 10.

type Slice []int

func (A Slice)Append(value int) {
	A1 := append(A, value)

	sh:=(*reflect.SliceHeader)(unsafe.Pointer(&A))
	fmt.Printf("A Data:%d,Len:%d,Cap:%d\n",sh.Data,sh.Len,sh.Cap)

	sh1:=(*reflect.SliceHeader)(unsafe.Pointer(&A1))
	fmt.Printf("A1 Data:%d,Len:%d,Cap:%d\n",sh1.Data,sh1.Len,sh1.Cap)
}

func main() {
	mSlice := make(Slice, 10, 10)
	mSlice.Append(5)
	fmt.Println(mSlice)
}

Запустив код, мы найдем два срезаDataУже не тот.

A  Data:824633835680,Len:10,Cap:10
A1 Data:824634204160,Len:11,Cap:20

Это потому, что вappendкогда это было найденоCapНедостаточно, был создан новыйDataМассив, используемый для хранения новых данных и одновременно расширяемыйCapемкость.

резюме

В конце концов, я еще раз ответил Dreamerque и поблагодарил Weelin, а потом я подумал о такой проблеме, может быть много друзей, которые столкнутся с ней, поэтому я написал статью, чтобы разобрать суть Slice, то есть SliceHeader, Я надеюсь, что это может помочь Всем, язык Go, golang, это действительно грубо, SliceHeader очень скользкий.

Эта статья является оригинальной статьей, перепечатайте и укажите источник, добро пожаловать на сканирование кода и обратите внимание на общедоступный номерflysnow_orgили сайтwww.flysnow.org/, и впервые прочитал последующие замечательные статьи. Если вы считаете, что это хорошо, пожалуйста, нажмите «Хорошо выглядит» в правом нижнем углу статьи, спасибо за вашу поддержку.

扫码关注