❝«Фаза 11» Осталось 69 выпусков до маленькой цели дяди из 80 выпусков., сегодня дядя будет с тобой болтать о -Параметр функции в golang — передается ли срез по ссылке или по значению.. Это очень простое и очень классическое интервью. Я считаю, что независимо от того, на каком крупном заводе вы идете на собеседование, можно сказать, что это обязательно, когда вы задаете вопрос о нарезке. Если вы отвечаете на него перед большой коровой, вы возможно, придется идти домой и ждать. Давайте узнаем вместе.
❞
Иллюзия параметров нарезки — передача по ссылке
Когда параметр функции в golang является срезом, передается ли он по ссылке или по значению? По этому вопросу, когда вы проходите раунд Baidu, вы обнаружите, что большое количество людей думают, что это передача по ссылке, и обычно они публикуют следующий код, чтобы доказать это:
pacakge main
func changeSlice(s []int) {
s[1] = 111
}
func main() {
slice := []int{0, 1, 2, 3}
fmt.Printf("slice: %v \n", slice)
changeSlice(slice)
fmt.Printf("slice: %v\n", slice)
}
В приведенном выше коде переменная slice инициализируется в основной функции, а затем вызывается функция changeSlice, параметром которой является переменная slice. Основная логика обработки функции changeSlice заключается в изменении значения второго элемента среза. Посмотрим на результат запуска печати:
slice: [0 1 2 3]
slice: [0 111 2 3]
Из результатов вывода мы видим, что модификация среза в функции changeSlice, переменная slice в основной функции также изменяется. На первый взгляд, разве это не производительность передачи по ссылке?
Но действительно ли это проходит цитату?
Разъясните три важных понятия
Обсуждая, как передаются параметры среза функции, давайте сначала проясним следующие три важных понятия:
- передача по значению (передача по значению)
- указатель прохода
- пройти по ссылке (пройти по ссылке)
передача по значению (передача по значению)
Это означает, что копия фактических параметров передается функции при вызове функции, поэтому изменение параметров в функции не повлияет на фактические параметры. Это простое не нужно вдаваться в подробности.
указатель прохода
Формальный параметр — это указатель на адрес фактического параметра.При работе с указателем формального параметра это эквивалентно работе с самим фактическим параметром. Звучит довольно запутанно, не так ли? Давайте посмотрим на пример:
func main() {
a := 10
pa := &a
fmt.Printf("value: %p\n", pa)
fmt.Printf("addr: %p\n", &pa)
modify(pa)
fmt.Println("a 的值被修改了,新值为:", a)
}
func modify(p *int) {
fmt.Printf("函数内的 value: %p\n", p)
fmt.Printf("函数内的 addr: %p\n", &p)
*p = 1
}
В приведенном выше коде определена переменная a, а адрес сохраняется в переменной-указателе pa, затем печатаются значение pa и адрес pa, затем вызывается функция модификации, а параметром является переменная-указатель pa. ; значение формального параметра p сначала выводится в функцию модификации и адрес p, а затем модифицируется значение p до 1.
Результат распечатываем:
value: 0xc000016088
addr: 0xc00000e028
函数内的 value: 0xc000016088
函数内的 addr: 0xc00000e038
a 的值被修改了,新值为: 1
Из вывода мы видим, чтоЭто копия указателя. Хотя значения указателей pa и p одинаковы, адреса памяти, где хранятся два указателя, разные, поэтому это два разных указателя.
Примечание: Все, что хранится в памяти, имеет свой собственный адрес, и указатели не являются исключением.Хотя они указывают на другие данные, у них также есть память для хранения указателя..
Думаю, в сочетании с картинкой будет понятнее:
"Посмотрите на изображение комментария 1"
пройти по ссылке (пройти по ссылке)
Это означает, что адрес фактического параметра передается функции при вызове функции, и модификация параметра в функции повлияет на фактический параметр.
Принимая приведенную выше демонстрацию в качестве примера, если адрес переменной указателя p, напечатанный в функции модификации, также равен 0xc00000e028, то мы считаем, что он передан по ссылке.
Но здесь мы не можем использовать go в качестве примера, см. ниже причину.
Официальное подавление: параметры функции Go передаются только по значению
прочитай этопередача по значению, передача по указателю, передача по ссылкеПосле концепции, если вы будете настаивать на том, что это проход по цитате, ну дядя вас тут прямо сшибет.
Согласно официальной документации Go:В Go есть только один способ передать параметры функции по значению. То есть в Go есть только один способ передать параметры функциям.Портал официальных документов
Если вы все еще не уверены, давайте посмотрим непосредственно на пример:
package main
import "fmt"
func changeSlice(s []int) {
fmt.Printf("func: %p \n", &s)
s[1] = 111
}
func main() {
slice := []int{0, 1, 2, 3}
fmt.Printf("slice: %v slice addr %p \n", slice, &slice)
changeSlice(slice)
fmt.Printf("slice: %v slice addr %p \n", slice, &slice)
}
распечатать:
slice: [0 1 2 3] slice addr 0xc0000a6020
func: 0xc0000a6060
slice: [0 111 2 3] slice addr 0xc0000a6020
Если параметр слайса функции передает ссылку, то в приведенном выше примере адрес слайса, напечатанный в основной функции, должен совпадать с адресом напечатанного слайса в функции changSlice, но это не так из вывода результат.
Так что здесь можно с большой уверенностью сказать:Неправильно говорить, что параметры среза в функциях Go передаются по ссылке, и также неправильно говорить, что передача по ссылке предназначена для трех типов данных: срез, карта и канал.
Суть параметров среза по-прежнему передается по значению
Из приведенного выше анализа следует, что метод передачи параметров среза не является передачей по ссылке. Наоборот, очень вероятно передать указатель, и при анализе раздела о передаче указателя мы можем знать, что передача указателя на самом деле является копией указателя.Формальный параметр и фактический параметр - это два разных указатели, но их значения совпадают. По сути, можно сказать, что это все еще передача по значению.
Это так? Вылезли вопросы дяди, указывающие, что 90% возможно, потом проверим остальные 10%.
структура среза
Давайте сначала посмотрим, как выглядит структура среза:
type slice struct {
array unsafe.Pointer
len int
cap int
}
type Pointer *ArbitraryType
Слайс, как следует из названия, является частью массива cut, его структура содержит три части, первая часть — это указатель на базовый массив, за которым следует размер слайса len и предел емкости слайса. (Он содержит переменные-члены-указатели.)
Приведенная выше структура выглядит немного запутанной и недостаточно интуитивной. Давайте создадим пример: массив arr := [5]int{0,1,2,3,4} для создания среза slice := arr[1:4 ] , результирующий срез выглядит следующим образом:
"Посмотрите на изображение комментария 2"
Посмотрите на другой пример:
func main() {
arr := [5]int{0, 1, 2, 3, 4}
slice1 := arr[1:4]
slice2 := arr[2:5]
// 打印一
fmt.Printf("arr %v, slice1 %v, slice2 %v arr addr: %p, slice1 addr: %p, slice2 addr: %p\n", arr, slice1, slice2, &arr, &slice1, &slice2)
// 打印二
fmt.Printf("arr[2] addr: %p, slice1[1] addr: %p, slice2[0] addr: %p\n", &arr[2], &slice1[1], &slice2[0])
arr[2] = 2222
// 打印三
fmt.Printf("arr: %v, slice1: %v, slice2: %v\n", arr, slice1, slice2)
slice1[1] = 1111
// 打印四
fmt.Printf("arr: %v, slice1: %v, slice2: %v\n", arr, slice1, slice2)
}
В приведенном выше коде мы создаем массив и генерируем два среза. Выведите их значения и соответствующие адреса. Кроме того, измените значение ячейки в массиве или срезе и наблюдайте за изменением значения ячейки в массиве и срезе:
arr [0 1 2 3 4], slice1 [1 2 3], slice2 [2 3 4] arr addr: 0xc000014090, slice1 addr: 0xc00000c080, slice2 addr: 0xc00000c0a0
arr[2] addr: 0xc0000140a0, slice1[1] addr: 0xc0000140a0, slice2[0] addr: 0xc0000140a0
arr: [0 1 2222 3 4], slice1: [1 2222 3], slice2: [2222 3 4]
arr: [0 1 1111 3 4], slice1: [1 1111 3], slice2: [1111 3 4]
- Это видно по результату печати одного: создаются два слайса, каждый из них имеет разные адреса
- Из результата печати двух видно: элементы слайса slice1[1] и slice2[0] имеют тот же адрес, что и элемент массива arr[2], указывая на то, что этиСрезы обмениваются данными в массиве arr
- Из печати 3 и печати 4 видно, что изменение данных в общей части массива и среза оказывает прямое влияние на них обоих, что еще раз подтверждает вывод второго пункта.
Из приведенного выше анализа мы можем узнать, что основная причина, по которой два разных среза могут влиять друг на друга, заключается в том, что указатели внутри срезов указывают на один и тот же источник данных, а источники данных, на которые указывают указатели двух срезов, имеют пересечение. .
Вернемся к проблеме срезов как параметров функции, потому чтоВ Go есть только один способ передать параметры функции по значению, поэтому, когда слайс используется в качестве параметра, он фактически является копией слайса, но в скопированном слайсе значения переменных-членов-указателей, содержащихся в скопированном слайсе, одинаковы, то есть источники данных, на которые они указывают, одинаковы, поэтому измените их в вызывающей функции. Формальные параметры могут повлиять на фактические параметры.
Go Function Pass по значению Сводка
Обычно мы называем тип данных фактического параметра, который можно напрямую изменить, изменив формальный параметр в процессе копирования с передачей по значению.тип ссылки.
Итак, мы можем подвести итог следующим образом:
Все параметры в языке Go передаются по значению (pass by value), что является копией, копией. Поскольку скопированное содержимое иногда не является ссылочным типом (int, string, struct и т. д.), исходные данные содержимого не могут быть изменены в функции; некоторые из них являются ссылочными типами (указатель, карта, срез, chan и т. д.). , поэтому вы можете изменить данные исходного контента.
Здесь следует отметить следующее:Ссылочные типы и передача по ссылке — это две концепции..
Параметры нарезки, влияет ли изменение формальных параметров на фактические параметры?
Да, видя это, мы должны задать себе вопрос: параметры среза функции, будет ли модификация формальных параметров внутри функции влиять на внешние фактические параметры? (Как только выйдет вопросительное предложение, должно быть привидение!)
Давайте посмотрим на пример:
func main() {
slice := make([]int, 2, 3)
for i := 0; i < len(slice); i++ {
slice[i] = i
}
fmt.Printf("slice: %v, addr: %p \n", slice, slice)
changeSlice(slice)
fmt.Printf("slice: %v, addr: %p \n", slice, slice)
}
func changeSlice(s []int){
s = append(s, 3)
s = append(s, 4)
s[1] = 111
fmt.Printf("func s: %v, addr: %p \n", s, s)
}
В приведенном выше коде сначала создается пустой слайс длиной 2 и емкостью 3, затем слайсу присваиваются два значения, а затем вызывается функция changeSlice, параметром которой является слайс с присвоенным ценность. Функция changeSlice сначала добавляет к срезу два элемента, а затем изменяет значение второго элемента среза.
Посмотрим на распечатку:
slice: [0 1], addr: 0xc00001a100
func s: [0 111 3 4], addr: 0xc000014090
slice: [0 1], addr: 0xc00001a100
Из результатов вывода видно, что после изменения формального параметра s в функции срез внешнего фактического параметра не изменяется, и отмечается, что значение переменной-указателя формального параметра s и фактического параметра slice отличается, то есть источники данных, на которые они указывают, не совпадают.
что насчет этого?
Расширение среза
Во-первых, давайте разберемся с точкой знаний:
И массивы, и срезы имеют ограничения по длине. То есть при добавлении слайса, если элемент находится в пределах диапазона емкости слайса, просто добавьте элемент в конец. Если максимальная емкость превышена, необходимо скопировать и расширить дополнительные элементы базового массива..
Это:
- При использовании метода append для добавления элементов в срез, поскольку емкость среза еще не заполнена, это эквивалентно расширению содержимого массива, на который указывает срез.
- При использовании метода append для добавления элементов в срез, если емкость среза в это время заполнена, а емкость среза превышена при добавлении, массив выйдет за границы и будет выполнена операция расширения.
Когда требуется расширение, что делает append?
- Создайте новый временный срез t. Длина t такая же, как длина среза среза, но емкость t вдвое больше, чем у среза. При создании нового среза нижний слой также создает анонимный массив. Длина массива равна емкости среза. (На самом деле, это не обязательно удваивает емкость, пожалуйста, обратитесь к статье для получения подробной информации.О стратегии расширения слайсов в Golang)
- Скопируйте элементы среза в t, то есть заполните анонимный массив. Затем присвойте t срезу, и теперь срез указывает на базовый анонимный массив.
- Преобразован в метод добавления с меньшей емкостью.
Например, массив arr = [3]int{0, 11, 22}, генерирующий срез slice := arr[1:3], используя метод append для добавления элемента 33 к слайсу, следующие операции будут происходить:
"Посмотрите на изображение комментария 3"
Возвращаясь к только что приведенному примеру, переменная slice внешнего аргумента slice не зависит от изменения формального параметра переменной slice s, потому что после выполненияs = append(s, 4)После этого кода массив, на который указывает указатель slice s, расширяется, и его указатель указывает на новый массив, поэтому, когда значение второго элемента снова изменяется, это не повлияет на внешнюю переменную slice.
Расчет емкости среза
Расчет конкретной емкости слайса может относиться к следующему примеру:
В массиве [0, 1, 2, 3, 4] массив имеет 5 элементов. Если срез s = [1, 2, 3], то индекс 3 в массиве равен 3, что соответствует размеру последнего элемента, оставшегося в массиве, плюс s уже имеет 3 элемента, поэтому емкость последнего с равно 1 + 3 = 4. Если срез s1 = [4], индекс 4 самый большой в массиве, пустой элемент массива равен 0, тогда емкость s1 равна 0 + 1 = 1. Конкретная таблица выглядит следующим образом:
"Посмотрите на картинку комментария 4"
Операция
Что ж, этот обмен подходит к концу.В конце я думаю, что необходимо оставить для вас задание, чтобы проверить ваше принятие вышеперечисленных пунктов знаний:
func main() {
slice := make([]int, 2, 3)
for i := 0; i < len(slice); i++ {
slice[i] = i
}
ret := changeSlice(slice)
ret[1] = 111
fmt.Printf("slice: %v, ret: %v \n", slice, ret)
}
func changeSlice(s []int) []int {
s[0] = 10
s = append(s, 3)
s = append(s, 4)
s[1] = 100
return s
}
// 问:最终打印输出什么内容?
Пожалуйста, напишите свой ответ в комментариях~
обрати внимание напублика"дядя говорит код"Чтобы получить больше сухих товаров, это конец сегодняшнего обмена, увидимся в следующем выпуске~
Ссылаться на:
1. HTTPS://wuwuwu.fly snow.org/2018/02/24/golang-function-parameters-passed-don't-value.HTML
2. https://segmentfault.com/a/1190000015246182
3. https://studygolang.com/articles/9876