Go Basics 17-Klar, welche Funktionen als verzögerte Funktionen verwendet werden können?

Die meisten Gopher verwenden gerne Defer, weil es die Funktionen prägnant und robust macht. Aber „Wenn Sie Ihre Arbeit gut machen wollen, müssen Sie zuerst Ihre Werkzeuge schärfen.“ Wenn Sie Defer verwenden möchten, müssen Sie im Voraus einige wichtige Aspekte des Defers verstehen, um nicht in unnötige „Gruben“ zu geraten.

  1. Klären Sie, welche Funktionen als verzögerte Funktionen verwendet werden können.
    Für benutzerdefinierte Funktionen oder Methoden kann defer bedingungslose Unterstützung bieten, aber für benutzerdefinierte
    Funktionen oder Methoden mit Rückgabewerten wird der Rückgabewert automatisch verworfen, wenn die verzögerte Funktion zur Ausführung geplant ist.
    Neben benutzerdefinierten Funktionen oder Methoden in der Go-Sprache gibt es auch integrierte Funktionen. Im Folgenden finden Sie eine vollständige Liste der integrierten Funktionen in der Go-Sprache
    :
    append cap close complex copy delete imag len
    make new panic print println real restart
    Können alle integrierten Funktionen als verzögerte Funktionen verwendet werden? Schauen wir uns das folgende Beispiel an:
    // kapitel4/sources/deferred_func_6.go
func bar() (int, int) {
    
    
return 1, 2
}
func foo() {
    
    
var c chan int
var sl []int
var m = make(map[string]int, 10)
m["item1"] = 1
m["item2"] = 2
var a = complex(1.0, -1.4)
var sl1 []int
defer bar()
defer append(sl, 11)
defer cap(sl)
defer close(c)
defer complex(2, -2)
defer copy(sl1, sl)
defer delete(m, "item2")
defer imag(a)
defer len(sl)
defer make([]int, 10)
defer new(*int)
defer panic(1)
defer print("hello, defer\n")
defer println("hello, defer")
defer real(a)
defer recover()
}
func main() {
    
    
foo()
}

Führen Sie das Beispiel aus:

./deferred_func_6.go:22:2: defer discards result of append(sl, 11)
./deferred_func_6.go:23:2: defer discards result of cap(sl)
./deferred_func_6.go:25:2: defer discards result of complex(2, -2)
./deferred_func_6.go:28:2: defer discards result of imag(a)
./deferred_func_6.go:29:2: defer discards result of len(sl)
./deferred_func_6.go:30:2: defer discards result of make([]int, 10)
./deferred_func_6.go:31:2: defer discards result of new(*int)
./deferred_func_6.go:35:2: defer discards result of real(a)

Der Go-Compiler gab eine Reihe von Fehlermeldungen aus! Daraus können wir ersehen, dass integrierte Funktionen wie „append“, „cap“, „len“, „make“ und „new“ nicht direkt als verzögerte Funktionen verwendet werden können , Schließen, Kopieren, Löschen, Drucken, Wiederherstellen usw. jedoch schon.

Für die integrierten Funktionen, die nicht direkt als verzögerte Funktionen verwendet werden können, können wir eine anonyme Funktion verwenden, die sie umschließt, um die Anforderungen indirekt zu erfüllen. Nehmen Sie als Beispiel das Anhängen:

defer func() {
    
    
_ = append(sl, 11)
}()

Die praktische Bedeutung davon muss jedoch von den Entwicklern selbst erkannt werden.

2. Erfassen Sie den Zeitpunkt der Auswertung des Ausdrucks nach dem Schlüsselwort defer

Beachten Sie, dass der Ausdruck nach dem Schlüsselwort defer ausgewertet wird, wenn die verzögerte Funktion im Stapel der verzögerten Funktionen registriert wird.

Im Folgenden wird ein typisches Beispiel verwendet, um den Auswertungszeitpunkt des Ausdrucks nach dem Schlüsselwort defer zu veranschaulichen:

func foo1() {
    
    
for i := 0; i <= 3; i++ {
    
    
	defer fmt.Println(i)
}
}
func foo2() {
    
    
	for i := 0; i <= 3; i++ {
    
    
		defer func(n int) {
    
    
		fmt.Println(n)
	}(i)
}
}

func foo3() {
    
    
for i := 0; i <= 3; i++ {
    
    
defer func() {
    
    
fmt.Println(i)
}()
}
}
func main() {
    
    
	fmt.Println("foo1 result:")
	foo1()
	fmt.Println("\nfoo2 result:")
	foo2()
	fmt.Println("\nfoo3 result:")
	foo3()
}

Wir analysieren nacheinander den Auswertungszeitpunkt der Ausdrücke nach dem Schlüsselwort defer in foo1, foo2 und foo3:
In foo1 folgt direkt auf defer die Funktion fmt.Println. Immer wenn defer fmt.Println im Stapel der verzögerten Funktion registriert, werden die Parameter angezeigt Das folgende Println wird ausgewertet. Gemäß der obigen Codelogik sind die Funktionen, die nacheinander in den verzögerten Funktionsstapel verschoben werden, wie folgt:

fmt.Println(0)
fmt.Println(1)
fmt.Println(2)
fmt.Println(3)

Daher wird nach der Rückkehr von foo1, wenn die Ausführung der verzögerten Funktion geplant ist, die auf den Stapel verschobene verzögerte Funktion
in LIFO-Reihenfolge aus dem Stapel entfernt, sodass das Ausgabeergebnis wie folgt lautet:

3
2
1
0

In foo2 folgt auf defer eine anonyme Funktion mit einem Parameter. Immer wenn defer eine anonyme Funktion im Stapel verzögerter Funktionen registriert
, werden die Parameter der anonymen Funktion ausgewertet. Gemäß der obigen Codelogik
sind die Funktionen, die nacheinander in den verzögerten Funktionsstapel verschoben werden, wie folgt:

func(0)
func(1)
func(2)
func(3)

Daher wird nach der Rückkehr von foo2, wenn die Ausführung der verzögerten Funktion geplant ist, die auf den Stapel verschobene verzögerte Funktion
in LIFO-Reihenfolge aus dem Stapel entfernt, sodass das Ausgabeergebnis wie folgt lautet:

3
2
1
0

In foo3 folgt auf defer eine anonyme Funktion ohne Parameter. Gemäß der obigen Codelogik sind die Funktionen, die nacheinander in
den verzögerten Funktionsstapel verschoben werden, wie folgt:

func()
func()
func()
func()

Daher wird nach der Rückkehr von foo3, wenn die Ausführung der verzögerten Funktion geplant ist, die auf den Stapel verschobene verzögerte Funktion
in LIFO-Reihenfolge aus dem Stapel entfernt. Die anonyme Funktion greift geschlossen auf die Variable i der Peripheriefunktion zu und gibt den
Wert von i über Println aus. Zu diesem Zeitpunkt ist der Wert von i 4, sodass das Ausgabeergebnis von foo3 lautet:

4
4
4
4

Da der Zeitpunkt der Auswertung von Verzögerungsausdrücken sehr wichtig ist, schauen wir uns ein weiteres Beispiel an:

func foo1() {
    
    
	sl := []int{
    
    1, 2, 3}
	defer func(a []int) {
    
    
	fmt.Println(a)
	}(sl)
	sl = []int{
    
    3, 2, 1}
	_ = sl
}
func foo2() {
    
    
	sl := []int{
    
    1, 2, 3}
	defer func(p *[]int) {
    
    
	fmt.Println(*p)
	}(&sl)
	sl = []int{
    
    3, 2, 1}
	_ = sl
}
func main() {
    
    
	foo1()
	foo2()
}

Lassen Sie uns die Funktionen foo1 und foo2 in diesem Beispiel analysieren.
In foo1 empfängt die anonyme Funktion nach defer einen Slice-Typ-Parameter. Wenn defer die anonyme Funktion im verzögerten Funktionsstapel registriert, werden ihre Parameter ausgewertet. Zu diesem Zeitpunkt ist der Wert der übergebenen Variablen sl [] int{1 , 2,3}, also ist die auf den verzögerten Funktionsstapel verschobene Funktion:

func([]int{
    
    1,2,3})

Obwohl sl später neu zugewiesen wird, ist der Parameterwert der verzögerten Funktion immer noch []int{1, 2, 3}, wenn foo1 zurückkehrt und die Ausführung der verzögerten Funktion geplant ist, sodass das Ausgabeergebnis von foo1 [1 2 3] ist. .

In foo2 empfängt die anonyme Funktion nach defer einen Slice-Pointer-Typ-Parameter. Wenn defer die anonyme Funktion im verzögerten Funktionsstapel registriert, werden ihre Parameter ausgewertet. Zu diesem Zeitpunkt ist der übergebene Parameter die Adresse der Variablen sl. , Die in den Stapel der verzögerten Funktionen verschobene Funktion lautet also:
Obwohl sl nach func(&sl) neu zugewiesen wird, ist der Parameterwert der verzögerten Funktion immer noch die Adresse von sl, wenn foo2 zurückkehrt und die Ausführung der verzögerten Funktion geplant ist Zeit sl Der Wert von ist zu []int{3, 2, 1} geworden, daher ist das Ausgabeergebnis von foo2 [3 2 1].

Supongo que te gusta

Origin blog.csdn.net/hai411741962/article/details/132808748
Recomendado
Clasificación