В продолжение предыдущей статьи продолжим обсуждение следующих вопросов:
- В чем разница между передачей по значению, передачей по указателю и передачей по ссылке при передаче параметров функции?
- зачем говоритьslice,map,channelявляется ссылочным типом?
- в ГоsliceЭто передача по ссылке при передаче функции? Если нет, то почему его значение может быть изменено внутри функции?
In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by valueк функции, и вызываемая функция начинает выполнение. Адрес документа: https://golang.org/ref/spec#Calls
В официальной документации четко указано:В Go есть только один способ передать параметры функции, дабы укрепить мое понимание, давайте еще раз разберем каждый способ передачи параметров.
передать по значению
Передача по значению относится к копированию фактических параметров в функцию при вызове функции, поэтому, если параметры будут изменены в функции, фактические параметры не будут затронуты.
Концепции всегда дают людям ощущение учебника, напишите код для проверки.
func main() {
a := 10
fmt.Printf("%#v\n", &a) // (*int)(0xc420018080)
vFoo(a)
}
func vFoo(b int) {
fmt.Printf("%#v\n", &b) // (*int)(0xc420018090)
}
Содержание комментария — это вывод моей машины, если вы запустите его, вы получите другой вывод.
Согласно коду для объяснения, так называемая передача значения: фактический параметрaпередается в функциюvFooпараметрbпосле, вvFooвнутренний,bбудет выделено место в стеке как локальная переменная и скопировано полностьюaзначение .
После выполнения кода мы видим следующий результат: a и b имеют совершенно разные адреса памяти, указывающие на то, что хотя они и имеют одинаковое значение (a скопировано b, значение должно быть одинаковым), но они находятся в разных местах в памяти, поэтому они находятся в разных местах памяти.vFooВнутренний, если измененbзначение ,aне пострадает.
Левая часть рисунка — это выделение памяти, когда она не была вызвана, а правая сторона — это переменные, выделяемые в память после вызова функции. Здесь следует отметить, что даже если имя параметра vFoo равно a, фактический параметр имеет свое собственное пространство памяти, потому что имя параметра предназначено только для программиста, и в предыдущей статье это было ясно.
передача указателя
Формальный параметр — это указатель на адрес фактического параметра.Когда операция выполняется над формальным параметром, она эквивалентна операции, выполняемой над самим фактическим параметром.
Облачно? Так называемая передача указателя также анализируется с помощью комбинации кода.
func main() {
a := 10
pa := &a
fmt.Printf("value: %#v\n", pa) // value: (*int)(0xc420080008)
fmt.Printf("addr: %#v\n", &pa) // addr: (**int)(0xc420088018)
pFoo(pa)
}
func pFoo(p * int) {
fmt.Printf("value: %#v\n", p) // value: (*int)(0xc420080008)
fmt.Printf("addr: %#v\n", &p) // addr: (**int)(0xc420088028)
}
Определена переменная a, и адрес сохраняется в переменной указателя pa. По нашему заключению,Только передача по значению в Go, то после того, как указательная переменная pa будет передана в формальный параметр p функции, формальный параметр будет его копией в стеке, и каждый из них будет иметь разные адреса, но значения двух одинаковы (обе переменные адреса). Раздел комментариев выше является результатом работы моей программы.Адреса pa и p не связаны друг с другом, что указывает на то, что копирование значения происходит во время передачи параметра.
в функцииpFooВ адрес формального параметра p не совпадает с адресом фактического параметра pa, но их значения в памяти являются адресом переменной a, поэтому значение a можно изменить с помощью операций, связанных с указателем .
На рисунке &a представляет адрес a, значение: 0xc420080008
пройти по ссылке
Так называемая передача по ссылке относится к передаче адреса фактического параметра в функцию при вызове функции, тогда модификация параметра в функции повлияет на фактический параметр.
Поскольку в Go нет передачи по ссылке, мы часто видим, что передача по ссылке в Go также предназначена для:Slice,Map,ChannelЭти типы (это неправильная идея), поэтому для того, чтобы объяснить передачу по ссылке, давайте взглянем на кусок кода C++ (конечно, он очень простой).
void rFoo(int & ref) {
printf("%p\n", &ref);// 0x7ffee5aef768
}
int main() {
int a = 10;
printf("%p\n", &a);// 0x7ffee7307768
int & b = a;
printf("%p\n", &b);// 0x7ffee5aef768
rFoo(b);
return 0;
}
Здесь просто определить ссылку в main и передать ее функцииrFoo, так что давайте посмотрим, как выглядит ортодоксальная передача по ссылке?
Здесь b — псевдоним a (цитата, см. мою последнюю статью, если непонятно), поэтому a и b должны иметь один и тот же адрес. Затем, в соответствии с определением передачи по ссылке, после передачи фактического параметра b формальному параметру ref, ref будет псевдонимом b (то есть a, b и ref — одна и та же переменная), и они будут иметь один и тот же адрес. вrFooИз информации о печати в функции видно, что все три имеют точно такой же адрес, который называется передачей по ссылке.
Нет передачи по ссылке в Go
Вызовы функций в Go передаются только по значению, но ссылки на типы имеют ссылочные типы, а именно:slice,map,channel. Давайте посмотрим на официальное заявление:
There's a lot of history on that topic. Early on, maps and channels were syntactically pointers and it was impossible to declare or use a non-pointer instance. Also, we struggled with how arrays should work. Eventually we decided that the strict separation of pointers and values made the language harder to use. Changing these types to act as references to the associated, shared data structures resolved these issues. This change added some regrettable complexity to the language but had a large effect on usability: Go became a more productive, comfortable language when it was introduced.
Это, вероятно, означает: синтаксис указателя использовался в начале, и он был изменен на ссылку по разным причинам, но эта ссылка отличается от ссылки C++, это структура, которая разделяет связанные данные. Подробное обсуждение этого вопроса я положу вsliceОбсуждалось в соответствующей статье, теперь вернемся к теме, обсуждаемой сегодня.
Итак, откуда взялась передача по ссылке в Go? Я думаю, что людей неправильно понимают из-за непонимания, вызванного ссылочными типами, такими как карта, срез и канал, которые передаются функции и значение которых может быть изменено внутри функции.
Ибо эти три типаby valuePass, мы используем slice для проверки.
func main() {
arr := [5]int{1, 3, 5, 6, 7}
fmt.Printf("addr:%p\n", &arr)// addr:0xc42001a1e0
s1 := arr[:]
fmt.Printf("addr:%p\n", &s1)// addr:0xc42000a060
changeSlice(s1)
}
func changeSlice(s []int) {
fmt.Printf("addr:%p\n", &s)// addr:0xc42000a080
fmt.Printf("addr:%p\n", &s[0])// addr:0xc42001a1e0
}
Массив определен в кодеarr, а затем используйте его для создания фрагмента. Если в go есть передача по ссылке, то адрес формального параметра s должен совпадать с фактическим параметром s1 (доказательство C++ выше).В реальной ситуации мы обнаружили, что у них совершенно разные адреса, т.е. есть, параметр еще копируется - передача значения .
Но вот странное явление, вы его виделиarrадрес сs[0]имеют один и тот же адрес, поэтому мы можем изменить слайс внутри функции, потому что когда он передается в функцию в качестве параметра, хотя сам слайс является копией значения, он внутренне ссылается на структуру соответствующего массива, поэтомуs[0]этоarr[0], поэтому его можно изменить.
резюме
- В Go есть только один способ передать параметры функции по значению;
- slice,map,channelОба являются ссылочными типами, но они отличаются от C++;
- sliceПолучив возможность передавать параметры через функцию, измените соответствующее значение массива, потому что указатель на массив ссылок хранится внутри среза, а не потому, что передается ссылка.
Следующая статья пытается разобрать его: Почему слайс нужно инициализировать с помощью make и что он делает при инициализации? Что он делает каждый раз, когда динамически расширяет свою емкость?