Heim >Backend-Entwicklung >Golang >Nutzen Sie acht Demos, um die fünf Hauptfunktionen von Go Language Defer zu verstehen

Nutzen Sie acht Demos, um die fünf Hauptfunktionen von Go Language Defer zu verstehen

藏色散人
藏色散人nach vorne
2023-04-23 17:40:511580Durchsuche

Verwenden Sie das Schlüsselwort defer in der Go-Sprache, um die Codeausführung bis zum Ende der Funktion zu verzögern. In der Entwicklung verwenden wir häufig das Schlüsselwort defer, um die Nacharbeit abzuschließen, z. B. das Schließen offener Dateideskriptoren, das Schließen von Verbindungen und das Freigeben von Ressourcen. defer 关键字可以将代码延迟到函数结束之前执行。在开发中,我们经常使用defer关键字完成善后工作,如关闭打开的文件描述符、关闭连接以及释放资源等。

func demo0() {
    fileName := "./test.txt"
    f, _ := os.OpenFile(fileName, os.O_RDONLY, 0)
    defer f.Close()

    contents, _ := ioutil.ReadAll(f)
    fmt.Println(string(contents))}

defer关键字一般紧跟在打开资源代码的后面,防止后续忘记释放资源,defer 声明的代码实际上要等到函数结束之前才会被执行。defer 虽然简单易用,但如果忽略了它的特性,就会在开发中面临困惑。于是,我总结了 defer 的五大特性,通过 8 个demo逐步介绍 defer 的特性。

特性1:多个 defer 时的调用顺序:先进后出

使用多个 defer 关键字时,先被声明的 defer 语句后被调用。类似于“栈”先进后出的特性,defer 的这一特性也很好理解,先被打开的资源,可能会被后续代码依赖,所以要后释放才安全。

func demo1() {
    for i := 0; i < 5; i++ {
        defer fmt.Println("defer:", i)
    }}// defer: 4// defer: 3// defer: 2// defer: 1// defer: 0

特性2:作用域为当前函数,不同函数下拥有不同的 defer 栈

运行 demo2 ,从结果中可以看出,第一个匿名函数和第二个匿名函数的 defer 执行顺序没有关系。
defer 作用域仅为当前函数,在当前函数最后执行,所以不同函数下拥有不同的 defer 栈。

func demo2() {
    func() {
        defer fmt.Println(1)
        defer fmt.Println(2)
    }()

    fmt.Println("=== 新生代农民工啊 ===")

    func() {
        defer fmt.Println("a")
        defer fmt.Println("b")
    }()}// 2// 1// === 新生代农民工啊 ===// b// a

特性3:defer 后的函数形参在声明时确认(预计算参数)

运行 demo3_1 ,根据结果,我们可以得出:defer 在声明时,就已经确认了形参n的值,而不是在执行时确认的;所以,后续变量 num 无论如何改变都不影响 defer 的输出结果。

func demo3_1() {
    num := 0
    defer func(n int) {
        fmt.Println("defer:", n)
    }(num)
    // 等同 defer fmt.Println("defer:", num)

    for i := 0; i < 10; i++ {
        num++
    }

    fmt.Println(num)}//10//defer: 0

运行 demo3_2,为什么这里 defer 的最终输出的结果会和变量 num 相同?因为这里使用的是指针。
defer 声明时,已经确认了形参p指针的指向地址,指向变量 num;后续变量 num 发生改变。所以在 defer 执行时,输出的是p指针指向的变量num的当前值。

func demo3_2() {
    num := 0
    p := &num    defer func(p *int) {
        fmt.Println("defer:", *p)
    }(p)

    for i := 0; i < 10; i++ {
        num++
    }

    fmt.Println(*p)}//10//defer: 10

再看一下 demo3_3,defer 打印的变量并没有通过函数参数传入,在defer执行时,才获取的”全局变量”num,所以 defer 输出结果与变量num一致。

func demo3_3() {
    num := 0
    defer func() {
        fmt.Println("defer:", num)
    }()

    for i := 0; i < 10; i++ {
        num++
    }

    fmt.Println(num)}//10//defer: 10

特性4:return 与 defer 执行顺序:return 先 defer 后

运行 demo4_1,可以发现 defer、return 都是在函数最后执行,但 return 先于 defer 执行;

func demo4_1() (int, error) {
    defer fmt.Println("defer")
    return fmt.Println("return")}// return// defer

这一点从输出结果上显而易见,但当 return、defer 的执行顺序和**函数返回值**“相遇”时,又将会产生许多复杂的场景。
在 demo4_2 中,函数使用命名返回值,最终输出结果为7。其中经历了这几个过程:

  1. (首先)变量 num 作为返回值,初始值为0;

  2. (其次)随后变量 num 被赋值为 10;

  3. (然后)return 时,变量 num 作为返回值被重新赋值为 2;

  4. (接着)defer 在 return 后执行,拿到变量 num 进行修改,值为7;

  5. (最后)变量 num 作为返回值,最终函数返回结果为7;

    func demo4_2() (num int) {
     num = 10
     defer func() {
         num += 5
     }()
    
     return 2}// 7

再来看一个例子。
在 demo4_3 中,函数使用匿名返回值

func demo4_3() int {
 num := 10
 defer func() {
     num += 5
 }()

 return 2}// 2
Das Schlüsselwort defer folgt im Allgemeinen dem Code zum Öffnen der Ressource, um zu verhindern, dass später vergessen wird, die Ressource freizugeben. Der von defer deklarierte Code wird erst am Ende der Funktion tatsächlich ausgeführt. Obwohl Defer einfach und leicht zu verwenden ist, kann es bei der Entwicklung zu Verwirrung kommen, wenn Sie seine Funktionen ignorieren. Daher habe ich die fünf Hauptfunktionen von Defer zusammengefasst und die Funktionen von Defer schrittweise anhand von 8 Demos eingeführt.
  1. Funktion 1: Aufrufreihenfolge bei Verwendung mehrerer Defer-Anweisungen: zuerst rein, zuletzt raus

    Wenn mehrere Defer-Schlüsselwörter verwendet werden, wird die zuerst deklarierte Defer-Anweisung später aufgerufen. Ähnlich wie die „Stack“-Funktion First In, Last Out ist auch diese Funktion des Aufschiebens leicht zu verstehen. Ressourcen, die zuerst geöffnet werden, können von nachfolgendem Code verwendet werden ist notwendig, um eine spätere Freigabe zu gewährleisten.
  2. func demo5_1() {
     defer fmt.Println(1)
     defer fmt.Println(2)
     defer fmt.Println(3)
    
     panic("没点赞异常") // 触发defer出栈执行
    
     defer fmt.Println(4) // 得不到执行}
  3. Funktion 2: Der Bereich ist die aktuelle Funktion, und es gibt verschiedene Verzögerungsstapel unter verschiedenen Funktionen.

    Führen Sie Demo2 aus. Aus den Ergebnissen ist ersichtlich, dass die Reihenfolge der Verzögerung der Ausführung der ersten anonymen Funktion und der zweiten anonymen Funktion ist hat keine Beziehung.
    Der Bereich der Verzögerung ist nur die aktuelle Funktion und wird am Ende der aktuellen Funktion ausgeführt, sodass es unter verschiedenen Funktionen unterschiedliche Verzögerungsstapel gibt.
  4. func demo5_2() {
     defer func() {
         if err := recover(); err != nil {
             fmt.Println(err, "问题不大")
         }
     }()
    
     panic("没点赞异常") // 触发defer出栈执行
    
     // ...}
  5. Funktion 3: Funktionsparameter nach der Verzögerung werden zum Zeitpunkt der Deklaration bestätigt (vorberechnete Parameter)

    Führen Sie demo3_1 aus, entsprechend den Ergebnissen, wir kann ausgegeben werden: defer Der Wert des formalen Parameters n wurde bei der Deklaration bestätigt, nicht bei der Ausführung. Nachfolgende Änderungen an der Variablen num haben daher keine Auswirkungen Ausgabe von defer.
  6. rrreee
  7. Führen Sie demo3_2 aus. Warum ist das endgültige Ausgabeergebnis von defer hier dasselbe wie die Variable num? Weil hier Zeiger verwendet werden.
    defer Bei der Deklaration wurde die Adresse, auf die der formale Parameter p-Zeiger zeigt, bestätigt und zeigt anschließend auf die Variable num. Wenn also defer ausgeführt wird, ist die Ausgabe der aktuelle Wert der Variablen num, auf die der p-Zeiger zeigt.

    rrreee

    Schauen Sie sich noch einmal Demo3_3 an. Die von defer gedruckten Variablen werden nicht über Funktionsparameter übergeben. Die „globale Variable“ num wird nur erhalten, wenn defer ausgeführt wird, sodass das Ausgabeergebnis von defer konsistent ist mit der Variablen num.
  8. rrreee
  9. Feature 4: Ausführungsreihenfolge von Return und Defer: Return zuerst Defer und dann

    Führen Sie Demo4_1 aus. Sie können feststellen, dass Defer und Return am Ende der Funktion ausgeführt werden, Return jedoch vor Defer.

    rrreee

    Das ist Dies ist aus den Ausgabeergebnissen ersichtlich. Aber wenn die Ausführungsreihenfolge von Return und Defer und **Funktionsrückgabewert** „übereinstimmt“, werden viele komplexe Szenarien auftreten.
    In demo4_2 verwendet die Funktion den benannten Rückgabewert und das endgültige Ausgabeergebnis ist 7. Es hat diese Prozesse durchlaufen:

    (erstens) wird die Variable num als Rückgabewert verwendet, mit einem Anfangswert von 0

    (zweitens) wird der Variablen num ein Wert von 10 zugewiesen;

  10. (then) Bei der Rückgabe wird die Variable num auf 2 als Rückgabewert neu zugewiesen;
🎜🎜🎜 (Then) defer wird nach der Rückgabe ausgeführt und die Variable num wird zur Änderung abgerufen , und der Wert ist 7; 🎜🎜🎜🎜 (Schließlich) Die Variable num wird als Rückgabewert verwendet und das endgültige Rückgabeergebnis der Funktion ist 7;
In demo4_3 verwendet die Funktion einen anonymen Rückgabewert und die Endergebnisausgabe ist 2. Der Vorgang ist wie folgt: Wenn 🎜🎜🎜🎜 die Funktion betritt, wird die Rückgabewertvariable zu diesem Zeitpunkt nicht erstellt. 🎜🎜🎜🎜 erstellt die Variable num und weist ihr den Wert 10 zu Die Rückgabewertvariable der Funktion wird erstellt und ihr wird der Wert 2 zugewiesen. Sie können diese Rückgabewertvariable als anonyme Variable oder als Variable a, b, c, d... betrachten, aber es ist nicht die Variable num; 🎜🎜Aufschub, egal wie Sie die Variable num ändern, es hat nichts mit dem Rückgabewert der Funktion zu tun; 🎜rrreee🎜Funktion 5: Wenn Panik auftritt, Die deklarierte Verzögerung springt aus dem Stapel und führt 🎜🎜Führen Sie demo5_1 aus. Sie können sehen, dass bei Auftreten einer Panik die deklarierte Verzögerung aus dem Stapel springt und dann in Panik gerät und die nach der Panik deklarierte Verzögerung nicht ausgeführt wird . 🎜rrreee🎜Mit dieser Funktion können Sie Panik durch Wiederherstellung in Verzögerung erfassen, um einen Absturz des Programms zu verhindern. 🎜rrreee🎜Angehängter🎜🎜vollständiger Code: 🎜github.com/newbugcoder/learngo/tre...🎜🎜🎜🎜

Das obige ist der detaillierte Inhalt vonNutzen Sie acht Demos, um die fünf Hauptfunktionen von Go Language Defer zu verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen