头图

Golang 基础之函数使用 (二)

大家好,今天将梳理出的 Go语言函数用法内容,分享给大家。 请多多指教,谢谢。

本次《Go语言函数使用》内容共分为三个章节,本文为第二章节。

本章节内容

  • 匿名函数
  • 闭包
  • 递归函数
  • 延迟调用 (defer)

匿名函数

介绍

在Go语言中,函数可以像普通变量一样被传递或使用,支持随时在代码里定义匿名函数。

匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

匿名函数有动态创建的特性,该特性使得匿名函数不用通过参数传递的方式,就可以直接引用外部的变量。

使用

第一种用法:将匿名函数赋给变量,在通过变量调用匿名函数

sum := func(a, b int) int {
  return a + b
}
fmt.Println(sum(1, 2)) // 输出 3

第二种用法:在定义匿名函数的时候直接使用,这种方式只能使用一次传参

sum := func(a, b int) int {
    return a + b
}(1,2) // 传入参数
fmt.Println(sum) // 输出 3

第三种用法:将匿名函数赋给一个全局变量,匿名函数在当前程序中都可以使用

package main

import "fmt"

var (
  // 全局变量必须首字母大写
    Sum = func(a, b int) int {
        return a + b
    }
)

func main() {
    sum := Sum(1, 2)
    fmt.Println("sum=", sum) // 输出 sum= 3
}

闭包

介绍

所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

闭包(Closure),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

可以理解为:闭包是匿名函数与匿名函数所引用环境的组合,类似常规函数引用全局变量处于一个包的环境。

闭包的优点

  • 变量可以常驻内存
  • 变量不污染全局
闭包里作用域返回的局部变量不会被立刻销毁回收,可能会占用更多内存过度使用闭包会导致性能下降。

使用

package main

import "fmt"

func main() {
    n := 0
    count := func() int {  // 这就是一个闭包
        n += 1
        return n
    }
    fmt.Println(count()) // 输出 1
    fmt.Println(count()) // 输出 2
}

常规函数、匿名函数 + 全局变量 + 包就等同于闭包, count不仅存储了函数的返回值,还存储了闭包的状态。

闭包被返回赋予一个同类型的变量时,同时赋值的是整个闭包的状态,该状态会一直存在外部被赋值的变量count中,直到count被销毁,整个闭包生命周期结束。

也可以写成下列形式

package main

import "fmt"

func Count() func() int { // 返回函数
    n := 0
    return func() int {
        n++
        return n
    }
}

func main() {
    count := Count()
    fmt.Println(count()) // 输出 1
    fmt.Println(count()) // 输出 2
}
高级闭包特性,比如并发中的闭包。 将放到后面章节为大家介绍。

递归函数

介绍

递归,就是在运行的过程中调用自己。

一个函数调用自己, 就叫做递归函数。

构成递归具备的条件:

  1. 子问题需要与原始问题为同样的事,且更为简单。
  2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。

使用

举例:数字阶乘

一个正整数的阶乘是所有小于及等于该数的正整数的积,并且0的阶乘为1。

package main

import "fmt"

func factorial(i int) int {  // 解读为 5*4*3*2*1=120 
    if i <= 1 {
        return 1
    }
    return i * factorial(i-1)
}

func main() {
    var i int = 5
    fmt.Printf("%d\n", factorial((i))) // 120
}
1808年,基斯顿·卡曼引进这个表示法。

举例:裴波那契数列(Fibonacci)

这个数列从第3项开始,每一项都等于前两项之和。

package main

import "fmt"

func fibonaci(i int) int {    
    if i == 0 {
        return 0    
    }
    if i == 1 {
        return 1
    }    
    return fibonaci(i-1) + fibonaci(i-2)
}

func main() {
    var i int
    for i = 0; i < 10; i++ {
        fmt.Printf("%d ", fibonaci(i)) // 0 1 1 2 3 5 8 13 21 34
    }
}

延迟调用 (defer)

介绍

在基础语法中已经介绍了defer延迟调用的使用,今天深入了解使用一下defer机制。

defer特性

  1. 关键字 defer 用于注册延迟调用。
  2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
  3. 多个defer语句,按先进后出的方式执行。
  4. defer语句中的变量,在defer声明时就决定了。
  5. 某个延迟调用发生错误,这些调用依旧会被执行。
defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。

defer用途

  1. 关闭文件句柄
  2. 锁资源释放
  3. 数据库连接释放

使用

多个 defer 注册,按 FILO 次序执行 ( 先进后出 ) 原则。

package main

func main() {
    defer println("1") // 先进来, 最后出去
    defer println("2")
    defer println("3") // 最后进来, 先出去
}

输出

3
2
1

延迟调用参数在注册时求值或复制,可以用指针或闭包 “延迟” 读取。

package main

import "fmt"

func main() {
    x, y := 10, 100
    defer func(i int) {
        fmt.Printf("defer x = %v, y = %v\n", i, y) // y 闭包引用
    }(x) // x 被复制

    x += 10
    y += 20
    println("x = ", x, "y = ", y)
}

输出

x =  20 y =  120
defer x = 10, y = 120

defer和return两者执行顺序

  1. 有名返回值 (函数返回值为已经命名的返回值)
package main

import "fmt"

func foo() (i int) { // 3.return i 值
    i = 0
    defer func() {
    fmt.Println(i) // 2.读取临时变量地址(返回值)
    }()
    return 2 // 1.返回值赋值,写入临时变量
}

func main() {
    foo()
}

输出

2

foo() 返回值的函数中 (这里返回值为 i),执行 return 2 的时候实际上已经将i 的值重新赋值为2。 所以defer closure输出结果为2。

解析:

return 的机制:1.首先将返回值放到一个临时变量中(为返回值赋值) 2. 然后将返回值返回到被调用处。

而defer函数恰在return的两个操作之间执行。

执行顺序是: 先为返回值赋值,即将返回值放到一个临时变量中,然后执行defer,然后return到函数被调用处。

b0wWvD.png

  1. 无名返回值 (即函数返回值为没有命名的函数返回值)
package main

import "fmt"

func foo() int {
    var i int
    defer func() {
        i++ // 这个地方 i,不是临时变量
        fmt.Println("defer = ", i) // 输出 defer = 1
    }()
    return i // 返回值赋值,写入临时变量
}

func main() {
    fmt.Println("return = ", foo()) // 输出 return = 0
}

解析:

return 先把返回值放到一个临时变量中,defer函数无法获取到这个临时变量地址 (没有函数返回值),所以无论defer函数做任何操作,都不会对最终返回值造成任何变动。

技术文章持续更新,请大家多多关注呀~~

搜索微信公众号【 帽儿山的枪手 】,关注我
短-白底-小文件.png


Golang从小白到大神
分享Golang语言技术交流
46 声望
12 粉丝
0 条评论
推荐阅读
Golang 基础之并发知识 (三)
大家好,今天将梳理出的 Go语言并发知识内容,分享给大家。 请多多指教,谢谢。本次《Go语言并发知识》内容共分为三个章节,本文为第三章节。Golang 基础之并发知识 (一)Golang 基础之并发知识 (二)Golang 基础之...

帽儿山的枪手1阅读 826

封面图
从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木75阅读 7.1k评论 16

从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木45阅读 8.5k评论 6

从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木35阅读 6.7k评论 10

一文搞懂秒杀系统,欢迎参与开源,提交PR,提高竞争力。早日上岸,升职加薪。
前言秒杀和高并发是面试的高频考点,也是我们做电商项目必知必会的场景。欢迎大家参与我们的开源项目,提交PR,提高竞争力。早日上岸,升职加薪。知识点详解秒杀系统架构图秒杀流程图秒杀系统设计这篇文章一万多...

王中阳Go33阅读 2.4k评论 1

封面图
从零搭建 Node.js 企业级 Web 服务器(五):数据库访问
回顾 从零搭建 Node.js 企业级 Web 服务器(一):接口与分层,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,控制层与服务层实现了业务处理过程,模型层定义了业务实体并以 对象-关系...

乌柏木34阅读 5k评论 9

从零搭建 Node.js 企业级 Web 服务器(十三):断点调试与性能分析
Node.js 官方提供了断点调试机制,出于安全性考虑默认为关闭状态,可以通过 node 参数 --inspect 或 --inspect-brk 开启,配合 IDE 能够非常方便地调试代码,本章就上一章已完成的项目 licg9999/nodejs-server-ex...

乌柏木31阅读 4.2k评论 9

46 声望
12 粉丝
宣传栏