関数の型は構造体のようなものであり、インスタンス化することができます 関数自体は情報を格納しません 参照環境と結合した後に形成されるクロージャのみが「メモリ」を持ちます 関数はコンパイル時の静的な概念であり、クロージャはランタイムダイナミクスの。
他のプログラミング言語でのクロージャ
クロージャは、一部のプログラミング言語ではラムダ式としても知られています。
環境内の変数へのクロージャの参照処理を「キャプチャ」と呼ぶこともあります。C ++ 11 標準では、キャプチャには参照とコピーの 2 種類があります。参照を変更できる元の値を「参照キャプチャ」と呼びます。 . プロシージャの値は、「コピー キャプチャ」と呼ばれるものを使用してクロージャにコピーされます。
Lua 言語では、キャプチャされた変数は Upvalue という名前になります。これは、キャプチャ プロセスが常にクロージャの上に定義された自由変数を参照するためです。
クロージャの実装も言語によって異なりますが、Lua言語ではクロージャも関数もプロトタイプの概念に属し、キャプチャした変数はUpvalueの形でクロージャへ参照されます。
C++ およびC#では、クロージャ用のクラスが作成され、キャプチャされた変数はコンパイル時にクラスのメンバーに配置されます。クロージャがキャプチャされた変数にアクセスするとき、実際にはクロージャの隠しクラスのメンバーにアクセスします。
クロージャ内の参照変数を変更する
クロージャはスコープの上部にある変数を変更でき、参照された変数を変更すると実際に変数が変更されます。これは次の例から理解できます。
// 文字列を準備する
str := "hello world"
// 無名関数を作成する
foo := func() {
// 無名関数内でstrにアクセスする
str = "hello dude"
}
// 無名関数を呼び出す
foo()
コードの説明は次のとおりです。
- 2 行目では、変更する文字列を準備します。
- 5 行目で、無名関数が作成されます。
- 8行目では、無名関数内ではstrが定義されておらず、無名関数よりも前にstrが定義されており、このときstrを無名関数へ参照してクロージャを形成しています。
- 12 行目でクロージャが実行され、この時点で str が変更され、hello dude になります。
コード出力:
hello dude
例: 閉鎖による記憶効果
クロージャでキャプチャされた変数は、クロージャ自体にメモリ効果を与えます。クロージャ内のロジックは、クロージャによってキャプチャされた変数を変更できます。変数は、クロージャの存続期間中常に存在します。クロージャ自体は、変数と同様にメモリを持ちます。効果。
アキュムレータの実装:
import main
import (
"fmt"
)
//値を提供して、関数を呼び出すたびに値を累算するようにしましょう
func Accumulate(value int) func() int {
//クロージャーを返す
return func() int {
//累算する
value++
//累算された値を返す
return value
}
}
func main() {
//1が初期値の累算器を作成する
accumulator := Accumulate(1)
//1を累算して印刷
fmt.Println(accumulator())
fmt.Println(accumulator())
//累算器の関数のアドレスを印刷する
fmt.Printf("%p\n", &accumulator)
//10が初期値の累算器を作成する
accumulator2 := Accumulate(10)
//1を累算して印刷
fmt.Println(accumulator2())
//累算器の関数のアドレスを印刷する
fmt.Printf("%p\n", &accumulator2)
}
コードの説明は次のとおりです。
- 8 行目、アキュムレータ生成関数。この関数は初期値を出力し、呼び出されたときに初期値に対して作成されたクロージャー関数を返します。
- 行 11 はクロージャー関数を返します。各戻り値は新しい関数インスタンスを作成します。
- 行 14 は、参照された Accumulate パラメータ変数を累積します。値は行 11 の匿名関数によって定義されていませんが、匿名関数によって参照されているため、クロージャを形成していることに注意してください。
- 17 行目は、クロージャの戻り値を通じて変更された値を返します。
- 行 24 は、初期値 1 のアキュムレータを作成します。返されるアキュムレータは、func()int 型の関数変数です。
- 27 行目で accumulator() が呼び出されると、コードは 11 行目から 17 行目に戻るまで匿名関数ロジックを実行します。
- 行 32、アキュムレータの関数アドレスを出力します。
出力ログを比較すると、accumulator と accumulator2 によって出力される関数アドレスが異なるため、これらは 2 つの異なるクロージャ インスタンスであることがわかります。
アキュムレータを呼び出すたびに、参照された変数が自動的に蓄積されます。
クロージャのメモリ効果は、デザイン パターンのファクトリー パターンと同様のジェネレーターを実装するために使用されます。次の例は、プレーヤー ジェネレーターを作成するプロセスを示しています。
プレーヤージェネレーターの実装:
package main
import (
"fmt"
)
//プレイヤーのジェネレーターを作成し、名前を入力してジェネレータを出力
func playerGen(name string) func() (string, int) {
//体力は常に150
hp := 150
//クロージャを作成して返す
return func() (string, int) {
//変数をクロージャに参照する
return name, hp
}
}
func main() {
//プレイヤージェネレータを作成
generator := playerGen("high noon")
//プレイヤーの名前と体力を返す
name、 hp := generator()
//値を出力する
fmt.Println(name、 hp)
}
コード出力は次のとおりです。
high noon 150
コードの説明は次のとおりです。
- 8 行目では、playerGen() でプレーヤー ジェネレーター関数を作成するための名前を指定する必要があります。
- 11 行目で、hp 変数を宣言して 150 に設定します。
- 14 行目から 18 行目では、匿名関数を参照して hp 変数と name 変数を参照し、クロージャを形成しています。
- 24 行目では、プレーヤー ジェネレーターは、パラメーター playerGen を渡して呼び出された後に取得されます。
- 27 行目で、プレーヤー ジェネレーター関数を呼び出して、プレーヤーの名前と HP を取得します。
Closure にも一定のカプセル化があり、11 行目の変数は playerGen のローカル変数であり、playerGen の外部から直接アクセスして変更することはできませんが、この機能もオブジェクト指向で重視されるカプセル化に似ています。