惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Latest
Security Latest
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Stack Overflow Blog
Stack Overflow Blog
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
GbyAI
GbyAI
云风的 BLOG
云风的 BLOG
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
宝玉的分享
宝玉的分享
博客园 - 【当耐特】
C
Cyber Attacks, Cyber Crime and Cyber Security
雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Threat Research - Cisco Blogs
NISL@THU
NISL@THU
Spread Privacy
Spread Privacy
P
Proofpoint News Feed
J
Java Code Geeks
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
MyScale Blog
MyScale Blog
T
Tor Project blog
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
P
Privacy & Cybersecurity Law Blog
MongoDB | Blog
MongoDB | Blog
Simon Willison's Weblog
Simon Willison's Weblog
C
Cybersecurity and Infrastructure Security Agency CISA
L
LINUX DO - 热门话题
小众软件
小众软件
G
GRAHAM CLULEY
P
Privacy International News Feed
AWS News Blog
AWS News Blog
Know Your Adversary
Know Your Adversary
P
Palo Alto Networks Blog
人人都是产品经理
人人都是产品经理
S
Schneier on Security
Scott Helme
Scott Helme
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
B
Blog RSS Feed
T
The Exploit Database - CXSecurity.com
Recent Announcements
Recent Announcements
E
Exploit-DB.com RSS Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
U
Unit 42
The Register - Security
The Register - Security
S
Securelist
Martin Fowler
Martin Fowler
Project Zero
Project Zero
大猫的无限游戏
大猫的无限游戏
Cisco Talos Blog
Cisco Talos Blog

博客园 - 自然洒脱

MiniMax 权益码 Token Plan 套餐 9 折优惠 go语言context包 go语言通道 go语言GMP模型 go语言mongodb操作 go语言gorm的CRUD go语言gorm go语言mysql驱动 go语言log相关 go语言包管理 go语言时间相关 go语言序列化和反序列化 go语言的"面向对象" go语言结构体排序 go语言异常处理 go语言接口 go语言结构体(二) go语言结构体(一) go语言函数作用域及匿名函数
go语言递归函数及defer
自然洒脱 · 2023-06-27 · via 博客园 - 自然洒脱

递归函数

简单来说,递归就是函数自己调用自己。有2种实现方式,一种是直接在自己函数中调用自己,一种是间接在自己函数中调用的其他函数中调用了自己。

递归函数需要有边界条件、递归前进段、递归返回段

递归一定要有边界条件,当边界条件不满足时,递归前进;当边界条件满足时,递归返回

func fib(n int) int {
    a, b := 1, 1
    if n <= 2 && n > 0 {
        return 1
    } else if n <= 0 {
        return 0
    }
    for i := 2; i < n; i++ {
        a, b = b, a+b
    }
    return b
}

// 递推公式变递归
func fib1(n int) int {
    switch {
    case n > 0 && n < 3:
        return 1
    case n == 0:
        return 0
    }
    return fib1(n-1) + fib1(n-2)
}

// 循环变递归,循环次数变成了函数调用次数
func fib2(a, b, n int) int {
    if n < 0 {
        panic("nagetive")
    } else if n == 0 {
        return 0
    } else if n >= 3 {
        a, b = b, a+b
        return fib2(a, b, n-1)
    } else {
        return b
    }
}

func main() {
    for i := 0; i < 10; i++ {
        fmt.Println(fib(i), fib1(i), fib2(1, 1, i))
    }
}

斐波那契数列

在循环层次变成递归函数层次中,n相当于循环变量,b和a+b就是每次循环体中使用的值

递归效率

在上面3个斐波那契数列函数实现中,如果进行时间计算,会发现fib1()函数效率最低,因为其中进行了大量的重复计算,所以慢。fib2()函数采用了递归函数调用层次代替循环层次,效率还不错,和循环版效率差不多。 但是函数每次递归,都会有压栈,递归到最后还要销毁栈帧,也会有资源开销。

对上面fib1函数进行优化后:

func fib(n int) int {
    a, b := 1, 1
    if n <= 2 && n > 0 {
        return 1
    } else if n <= 0 {
        return 0
    }
    for i := 2; i < n; i++ {
        a, b = b, a+b
    }
    return b
}

// 递推公式变递归
func fib1(n int) int {
    switch {
    case n > 0 && n < 3:
        return 1
    case n == 0:
        return 0
    }
    return fib1(n-1) + fib1(n-2)
}

func fib11(n int, m map[int]int) int {
    switch {
    case n > 0 && n < 3:
        return 1
    case n == 0:
        return 0
    }
    // 查询map中是否已存在
    v, ok := m[n]
    if ok {
        return v
    } else {
        v = fib11(n-1, m) + fib11(n-2, m)
        m[n] = v
        return v
    }
}

func fib12(n int, s []int) int {
    switch {
    case n > 0 && n < 3:
        return 1
    case n == 0:
        return 0
    }
    // 查询map中是否已存在
    if s[n] != 0 {
        return s[n]
    } else {
        s[n] = fib12(n-1, s) + fib12(n-2, s)
        return s[n]
    }
}

// 循环变递归,循环次数变成了函数调用次数
func fib2(a, b, n int) int {
    if n < 0 {
        panic("nagetive")
    } else if n == 0 {
        return 0
    } else if n >= 3 {
        a, b = b, a+b
        return fib2(a, b, n-1)
    } else {
        return b
    }
}

func main() {
    num := 100000
    start_time := time.Now()
    dig := fib(num)
    end_time := time.Now()
    fmt.Printf("%v fib函数用时: %v\n", dig, end_time.Sub(start_time))
    // start_time = time.Now()
    // dig = fib1(num)
    // end_time = time.Now()
    // fmt.Printf("%v fib1函数用时: %v\n", dig, end_time.Sub(start_time))
    start_time = time.Now()
    dig = fib2(1, 1, num)
    end_time = time.Now()
    fmt.Printf("%v fib2函数用时: %v\n", dig, end_time.Sub(start_time))
    start_time = time.Now()
    m0 := make(map[int]int, 100)
    dig = fib11(num, m0)
    end_time = time.Now()
    fmt.Printf("%v fib11函数用时: %v\n", dig, end_time.Sub(start_time))
    start_time = time.Now()
    s0 := make([]int, num+1, num+1)
    dig = fib12(num, s0)
    end_time = time.Now()
    fmt.Printf("%v fib12函数用时: %v\n", dig, end_time.Sub(start_time))
}

优化后并对比

 当num为100000时,明显看出各个函数哪个效率是最高的了。fib12函数和fib11函数的不同之处是采用的缓存方式不同,由于fib12采用的是切片,fib11采用的是hash表,采用切片,省去了hash计算,节省了很多时间。

间接递归

间接递归调用,是函数通过别的函数调用了自己,这一样也是递归。 只要是递归调用,不管是直接还是间接,都需要注意边界返回问题。但是间接递归调用有时候是非常不明显,代码调用复杂时,很难发现出现了递归调用,这是非常危险的。

func foo() {
 bar()
}
func bar() {
 foo()
}
foo()

递归是一种很自然的表达,符合逻辑思维

递归相对运行效率低,每一次调用函数都要开辟栈帧

递归有深度限制,如果递归层次太深,函数连续压栈,栈内存就可能溢出了

如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果

绝大多数递归,都可以使用循环实现

函数嵌套

package main
import "fmt"
func outer() {
 c := 99
 var inner = func() {
 fmt.Println("1 inner", c, &c)
 }
 inner()
 fmt.Println("2 outer", c, &c)
}
func main() {
 outer()
}

到outer中定义了另外一个函数inner,并且调用了inner。outer是包级变量,main可见,可以调 用。而inner是outer中的局部变量,outer中可见。

闭包

自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量。

闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript。闭包是运行期动态的概念。

  • 函数有嵌套,函数内定义了其它函数
  • 内部函数使用了外部函数的局部变量
  • 内部函数被返回(非必须)
package main
import "fmt"
func outer() func() {
 c := 99
 fmt.Printf("outer %d %p\n", c, &c)
 var inner = func() {
 fmt.Printf("inner %d %p\n", c, &c)
 }
 return inner
}
func main() {
 var fn = outer()
 fn()

首先有嵌套函数,也就是有嵌套作用域,inner函数中用到了c,但是它没有定义c,而外部的outer有局部变量c。

代码分析

  • 第15行调用outer函数并返回inner函数对象,并使用标识符fn记住了它。outer函数执行完了,其 栈帧上的局部变量应该释放,包括inner函数,因为它也是局部的。但是,c、inner对应的值都不 能释放,因为fn要用。所以这些值不能放在栈上,要放到堆上。在Go语言中,这称为变量逃逸,逃逸到堆上
  • 在某个时刻,fn函数调用时,需要用到c,但是其内部没有定义c,它是outer的局部变量,如果这 个c早已随着outer的调用而释放,那么fn函数调用一定出现错误,所以,这个outer的c不能释放, 但是outer已经调用完成了,怎么办?闭包,让inner函数记住自由变量c(逃逸到堆上的内存地址)

 defer

defer意思是推迟、延迟。语法很简单,就在正常的语句前加上defer就可以了。

在某函数中使用defer语句,会使得defer后跟的语句进行延迟处理,当该函数即将返回时,或发生 panic时,defer后语句开始执行。

注意os.Exit不是这两种情况,不会执行defer。

同一个函数可以有多个defer语句,依次加入调用栈中(LIFO),函数返回或panic时,从栈顶依次执行 defer后语句。

执行的先后顺序和注册的顺序正好相反,也就是后注册的先执行。 defer后的语句必须是一个函数或方法的调用。