Группа снова спорит, Go передает по значению или по ссылке?

задняя часть Go

Всем привет, я жареная рыба.

Несколько дней назад в нашей чат-группе Go небольшой партнер спросил: «Является ли xxx эталонным типом?» Этот вопрос вызвал почти 5 часов обсуждения:

图片

Возвращаясь к проблеме Nikkei, можно сказать, что споры вокруг нее происходят почти каждый месяц. то естьЯвляется ли язык Go передачей по значению (pass-by-value) или передачей по ссылке (pass-by-reference)?

Перейти официальное определение

Этот раздел относится к «Когда параметры функции передаются по значению?» официального FAQ по Go, который выглядит следующим образом.

Как и во всех языках семейства C,Все в Go передается по значению. То есть функция всегда получает копию того, что передается, как если бы существовал оператор присваивания, присваивающий значение параметру.

图片

Например:

  • Передайте значение int функции, и вы получите копию int.

    И передача значения указателя дает вам копию указателя, но не данных, на которые он указывает.

  • Карты и срезы ведут себя как указатели: они представляют собой дескрипторы, содержащие указатели на базовые данные карты или среза.

  • Копирование значения карты или среза не приводит к копированию данных, на которые оно указывает.

  • Копирование значения интерфейса копирует то, что хранится в значении интерфейса.

  • Если значение интерфейса содержит структуру, копирование значения интерфейса копирует структуру. Если значение интерфейса содержит указатель, копирование значения интерфейса копирует указатель, но не копирует данные, на которые он указывает.

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

передача по значению и передача по ссылке

пройти по значению

Передача по значению, также известная как передача по значению. ЭтоОтносится к копии фактических параметров, передаваемых функции при вызове функции., так что если параметр будет изменен в функции, это не повлияет на фактический параметр.

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

Случай 1 выглядит следующим образом:

func main() {
 s := "脑子进煎鱼了"
 fmt.Printf("main 内存地址:%p\n", &s)
 hello(&s)
}

func hello(s *string) {
 fmt.Printf("hello 内存地址:%p\n", &s)
}

Выходной результат:

main 内存地址:0xc000116220
hello 内存地址:0xc000132020

Мы видим, что переменная s в основной функции по адресу памяти равна0xc000116220. После передачи параметров функции hello внутренний адрес памяти, который она выводит, будет0xc000132020, оба изменились.

图片

Исходя из этого, можно сделать вывод, что в языке Go действительно все передается по значению. Означает ли это, что изменение значения внутри функции не повлияет на основную функцию?

Второй случай следующий:

func main() {
 s := "脑子进煎鱼了"
 fmt.Printf("main 内存地址:%p\n", &s)
 hello(&s)
 fmt.Println(s)
}

func hello(s *string) {
 fmt.Printf("hello 内存地址:%p\n", &s)
 *s = "煎鱼进脑子了"
}

Мы изменили значение переменной s в функции hello, поэтому какое значение переменной s мы выводим в основной функции в конце. Это "Жареная рыба в мозгу" или "Жареная рыба в мозгу"?

Выходной результат:

main 内存地址:0xc000010240
hello 内存地址:0xc00000e030
煎鱼进脑子了

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

Потому что «если переданное значение является адресом, указывающим на область памяти, то эту область памяти можно изменить».

То есть эти два адреса памяти фактически являются указателями на указатели, и все их корни указывают на один и тот же указатель, то есть на переменную s. Поэтому далее модифицируем переменную s и получаем результат вывода «Жареная рыба в мозг».

пройти по ссылке

Передача по ссылке, также известная как передача по ссылке.Относится к передаче адреса фактического параметра непосредственно в функцию при вызове функции., тогда изменение параметра в функции повлияет на фактический параметр.

На языке Go официал дал понять, что прохода по ссылке нет, то есть прохода по ссылке нет.

Поэтому текст просто описан, как в примере, даже если передать параметры, конечный выходной адрес памяти тот же.

Самая спорная карта и слайс

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

На самом деле в FAQ есть важное напоминание: «карты и слайсы ведут себя как указатели, они представляют собой дескрипторы, содержащие указатели на базовые данные карты или слайса».

map

Чтобы увидеть пример, разверните тип карты:

func main() {
 m := make(map[string]string)
 m["脑子进煎鱼了"] = "这次一定!"
 fmt.Printf("main 内存地址:%p\n", &m)
 hello(m)

 fmt.Printf("%v", m)
}

func hello(p map[string]string) {
 fmt.Printf("hello 内存地址:%p\n", &p)
 p["脑子进煎鱼了"] = "记得点赞!"
}

Выходной результат:

main 内存地址:0xc00000e028
hello 内存地址:0xc00000e038

Он действительно передается по значению, каким должен быть результат модифицированной карты. Поскольку это передача по значению, должно быть «на этот раз!», верно?

Выходной результат:

map[脑子进煎鱼了:记得点赞!]

В результате модификация выполнена успешно, и выводится сообщение «Не забудьте поставить лайк!». Это смущает, почему он передается по значению, и он также может добиться аналогичного эффекта ссылки, может ли он быть изменен на исходное значение?

Маленькая хитрость вот в чем:

func makemap(t *maptype, hint int, h *hmap) *hmap {}

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

То есть, когда мы звонимhelloметод, это эквивалентно передаче параметра указателяhello(*hmap), аналогично предыдущему случаю типов значений.

Мы называем такую ​​ситуацию «ссылочным типом», но «ссылочный тип» — это не то же самое, что передача по ссылке или передача по ссылке, все же есть четкая разница.

Подобно типу карты в Go, тип chan:

func makechan(t *chantype, size int) *hchan {}

тот же эффект.

slice

Чтобы увидеть пример, разверните тип среза:

func main() {
 s := []string{"烤鱼", "咸鱼", "摸鱼"}
 fmt.Printf("main 内存地址:%p\n", s)
 hello(s)
 fmt.Println(s)
}

func hello(s []string) {
 fmt.Printf("hello 内存地址:%p\n", s)
 s[0] = "煎鱼"
}

Выходной результат:

main 内存地址:0xc000098180
hello 内存地址:0xc000098180
[煎鱼 咸鱼 摸鱼]

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

Обратите внимание на две детали:

  • Бесполезный&чтобы получить адрес.

  • можно использовать напрямую%pпечатать.

Причина, по которой вы можете делать оба вышеперечисленных действия одновременно, заключается в том, что стандартная библиотекаfmtОптимизировано для этой части:

func (p *pp) fmtPointer(value reflect.Value, verb rune) {
 var u uintptr
 switch value.Kind() {
 case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
  u = value.Pointer()
 default:
  p.badVerb(verb)
  return
 }

обратите внимание на кодvalue.Pointer, стандартная библиотека провела специальную обработку, адрес указателя непосредственно соответствующего значения, конечно, не должен принимать символ адреса.

стандартная библиотекаfmtЭто также является причиной возможности вывода значения, соответствующего типу среза:

func (v Value) Pointer() uintptr {
 ...
 case Slice:
  return (*SliceHeader)(v.ptr).Data
 }
}

type SliceHeader struct {
 Data uintptr
 Len  int
 Cap  int
}

который внутренне преобразованDataСвойство, это именно тип производительности SliceHeader во время выполнения SliceHeader. Мы называем%pНа выходе сохраняется адрес элемента массива под выходным срезом.

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

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

Замечательно или нет?

Суммировать

В сегодняшней статье мы дали базовое объяснение и анализ вопроса Nikkei о языке Go: «Является ли язык Go передачей по значению (pass by value) или передачей по ссылке (pass by reference)».

Кроме того, в отрасли большинство людей путают такие типы, как slice, map, chan и т. д., которые считаются «передачей по ссылке», и поэтому думают, что xxx в языке Go передается по ссылке, а мы также продемонстрировали этот случай.

На самом деле это неправильное восприятие, потому что: «Если переданное значение является адресом, указывающим на пространство памяти, это пространство памяти может быть изменено».

Он действительно делает копию, но также использует различные средства (по сути, передачу указателей) для достижения эффекта модификации исходных данных, являющихся ссылочным типом.

Каменный молот, язык го имеет значение только для передачи,

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

Статья постоянно обновляется, вы можете прочитать ее в WeChat, выполнив поиск по запросу [Brain Fried Fish], эта статьяGitHub GitHub.com/Vicious Genetics/Нет...Он был включен, добро пожаловать в Star, чтобы призвать больше.

Ссылаться на

  • Перейти в группу читателей

  • When are function parameters passed by value?

  • Java передается по значению или по ссылке?

  • Передача параметров в языке Go осуществляется по значению или по ссылке?