Детали закрытия языка Go

Go

Оригинальная ссылка:Детали закрытия языка Go

Что такое закрытие?Замыкание — это сущность, состоящая из функции и связанной с ней эталонной среды.

Давайте возьмем несколько примеров, чтобы проиллюстрировать замыкания в Go и проблемы, вызванные ссылками на замыкания.

Go 语言闭包详解
Детали закрытия языка Go

функциональная переменная (значение функции)

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

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

func square(x int) {
	println(x * x)
}
  1. Звоните напрямую:square(1)
  2. Назначайте функции, как если бы они были переменными:s := square; затем можно вызвать функциональную переменную:s(1).Примечание: здесьsquareСкобки после вызова не ставятся.
  • перечислитьnilФункциональная переменная будет паниковать.
  • Нулевое значение функциональной переменной равноnil, что означает, что за ним может следоватьnilсравнение, но не между двумя функциональными переменными.

Закрытие

Теперь давайте проиллюстрируем замыкания примерами:

func incr() func() int {
	var x int
	return func() int {
		x++
		return x
	}
}

Вызов этой функции возвращает функциональную переменную.

i := incr(): Назначив эту функциональную переменнуюi,iсталЗакрытие.

такiсохранить правоxцитаты, мыслимыеу меня есть указатель на хилиу меня есть адрес x в нем.

так какiиметь точку зренияxуказатель, поэтому его можно изменитьx, и сохранить состояние:

println(i()) // 1
println(i()) // 2
println(i()) // 3

Это,xСбежав, его жизнь не заканчивается с окончанием его области действия.

Но этот код не увеличивается:

println(incr()()) // 1
println(incr()()) // 1
println(incr()()) // 1

Это потому, что он вызывается здесь три разаincr(), который возвращает три замыкания, ссылающиеся на три разныхx, их состояния независимы друг от друга.

ссылка на закрытие

Теперь давайте начнем с примера, чтобы проиллюстрировать проблемы, вызванные замыкающими ссылками:

x := 1
f := func() {
	println(x)
}
x = 2
x = 3
f() // 3

Поскольку замыкание является внешней лексической переменной области видимости.Цитировать, поэтому этот код выведет3.

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

Однако этот код выведет1:

x := 1
func() {
	println(x) // 1
}()
x = 2
x = 3

Это легче понять, преобразовав его во что-то вроде этого:

x := 1
f := func() {
	println(x)
}
f() // 1
x = 2
x = 3

Это потому чтоfЗначение было разыменовано при вызове, и последующие модификации не имеют к этому никакого отношения.

Но если вы снова позвонитеfвсе равно будет выводить3, что еще раз доказываетfхранится вxадрес г.

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

x := 1
func() {
	println(&x) // 0xc0000de790
}()
println(&x) // 0xc0000de790

Вы можете видеть, что ссылка на тот же адрес.

Справочник по круговым замыканиям

Следующие три примера иллюстрируют проблемы, создаваемые замыкающими ссылками внутри циклов:

первый пример

for i := 0; i < 3; i++ {
	func() {
		println(i) // 0, 1, 2
	}()
}

Этот код эквивалентен:

for i := 0; i < 3; i++ {
	f := func() {
		println(i) // 0, 1, 2
	}
	f()
}

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

второй пример

Обычный код: вывод0, 1, 2:

var dummy [3]int
for i := 0; i < len(dummy); i++ {
	println(i) // 0, 1, 2
}

Однако этот код выведет3:

var dummy [3]int
var f func()
for i := 0; i < len(dummy); i++ {
	f = func() {
		println(i)
	}
}
f() // 3

Ранее я упоминал, что замыкание принимает ссылку, поэтому этот код должен выводитья последнее значение 2Правильно?

неправильный. Это потому чтоiОкончательное значение не2.

Преобразование цикла в эту форму легче понять:

var dummy [3]int
var f func()
for i := 0; i < len(dummy); {
	f = func() {
		println(i)
	}
	i++
}
f() // 3

iдобавлен3выскочит из петли, поэтому после окончания петлиiОкончательное значение3.

так что используйтеfor rangeДля реализации этого примера это не будет работать так:

var dummy [3]int
var f func()
for i := range dummy {
	f = func() {
		println(i)
	}
}
f() // 2

Это потому чтоfor rangeиforБазовая реализация отличается.

третий пример

var funcSlice []func()
for i := 0; i < 3; i++ {
	funcSlice = append(funcSlice, func() {
		println(i)
	})

}
for j := 0; j < 3; j++ {
	funcSlice[j]() // 3, 3, 3
}

Выходная последовательность3, 3, 3.

Посмотрев на предыдущий пример, здесь легко понять: Все три функции ссылаются на одну и ту же переменную (i), так послеiУвеличение, значение, полученное путем разыменования, также будет увеличиваться, поэтому все три функции будут выводить3.

Код для добавления выходного адреса доказывает, что:

var funcSlice []func()
for i := 0; i < 3; i++ {
	println(&i) // 0xc0000ac1d0 0xc0000ac1d0 0xc0000ac1d0
	funcSlice = append(funcSlice, func() {
		println(&i)
	})

}
for j := 0; j < 3; j++ {
	funcSlice[j]() // 0xc0000ac1d0 0xc0000ac1d0 0xc0000ac1d0
}

Вы можете видеть, что ссылки на три функцииiадрес г.

Решение

1. Объявите новую переменную:

  • Объявить новые переменные:j := i, и поставить справа послеiОперация меняется наjработать.
  • Объявите новую переменную с тем же именем:i := i.Примечание. Правая часть короткого оператора здесь — это внешняя область видимости.i, левая сторона — это недавно объявленная область действия на этом уровне.i. Принцип тот же, что и выше.

Это эквивалентно объявлению переменной для каждой из трех функций, всего 3. Начальные значения этих трех переменных соответствуют значениям в цикле.iИ после этого не изменится.

2. Объявить новую анонимную функцию и передать параметры:

var funcSlice []func()
for i := 0; i < 3; i++ {
	func(i int) {
		funcSlice = append(funcSlice, func() {
			println(i)
		})
	}(i)

}
for j := 0; j < 3; j++ {
	funcSlice[j]() // 0, 1, 2
}

в настоящее времяprintln(i)в использованииiпередается параметрами функции, а параметры функции в языке Go передаются по значению.

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

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

Ссылка на ссылку

Библия языка го — анонимные функции