✨✨ 欢迎大家来到景天科技苑✨✨
?? 养成好习惯,先赞后看哦~??
? 作者简介:景天科技苑
?《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
?《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。
所属的专栏:Go语言开发零基础到高阶实战
景天的主页:景天科技苑
文章目录
函数进阶1. 延迟函数(defer)2. 函数的数据类型3. 函数的本质4. 匿名函数5. 回调函数6. 闭包闭包计算斐波那契数
函数进阶
1. 延迟函数(defer)
defer语句在Go语言中是一个强大的特性,它允许你延迟函数的执行直到包含它的函数即将返回。这通常用于清理资源、解锁互斥锁、记录时间、关闭文件等操作。
比如说我们打开一个文件,我们判断它有没关闭,我们希望所有代码执行完,最后调用defer函数来关闭文件
语法格式:
defer 函数调用
注意:
defer语句会将其后的函数调用压入一个栈中,当外层函数即将返回时,这些被defer的函数会按照后进先出(LIFO)的顺序执行。
defer可以在函数中多次使用,以注册多个需要延迟执行的函数。
package mainimport "fmt"// deferfunc main() { f("1") fmt.Println("2") //其他语句执行完,再执行defer包含的函数 defer f("3") fmt.Println("4")}func f(s string) { fmt.Println(s)}
可以看到3最后打印
流程分析
defer函数或者方法:一个函数或方法的执行被延迟了
你可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回,特别是当你在进行一些打开资源的操作时i/o 流,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题如果有很多调用 defer,那么 defer 是采用 后进先出(栈) 模式。你可以在函数中添加多个defer语句,当没有加defer的语句执行完毕后,这些defer语句会按照逆序执行多个defer案例:
package mainimport "fmt"// defer 作用:处理一些善后的问题,比如错误,文件、网络流关闭等等操作。// 特点,多个defer的问题// 不加defer的语句还是按顺序从上而下执行// 你可以在函数中添加多个defer语句,当没有加defer的语句执行完毕后,这些defer语句会按照逆序执行func main() { f("1") defer fmt.Println("2") defer f("3") fmt.Println("4") defer f("5") fmt.Println("6") defer f("7") fmt.Println("8")}func f(s string) { fmt.Println(s)}
defer存在传递参数:
在defer的时候,函数其实已经被调用了,但是没有执行。参数是已经传递进去的了
package mainimport "fmt"// defer传参的调用时机func main() { n := 10 fmt.Println("main n=", n) // 在defer的时候,函数其实已经被调用了,但是没有执行。参数是已经传递进去的了 defer ff(n) // 问题,defer延迟执行函数,参数时什么时候传递进去?在其他地方虽然n已经加1了,ff函数最后执行,但是ff函数中的n还是10,说明参数是先加载进来,只是没执行函数 n++ fmt.Println("main end n=", n) defer ff(n) //如果ff在n加1之后defer延迟执行,参数中的n是加1后的n}func ff(n int) { fmt.Println("ff函数中n=", n)}
defer后续常用场景:程序会报错: 异常(程序执行的时候突然报错了)、错误(我们开发的时候知道的预期错误)
善后工作:defer 处理异常。
2. 函数的数据类型
func (xxxx,xxx) xxx,xxxx函数也是一种数据类型,可以定义函数类型的变量package mainimport "fmt"// 函数是什么(数据类型)func main() { a := 10.01 fmt.Printf("%T\n", a) // 查看变量的类型 b := [4]int{1, 2, 3, 4} fmt.Printf("%T\n", b) // 查看变量的类型 c := true fmt.Printf("%T\n", c) // 查看变量的类型 // 函数的类型 func1() // 带了括号是函数的调用 fmt.Printf("%T\n", func1) // 查看变量的类型 func() fmt.Printf("%T\n", func2) // 查看变量的类型 func(int, int, ...string) (int, int) // func(int, int) (int, int) // func(int, int, ...string) (int, int) //var fun3 func(int, int, ...string) (int, int) fun3 := func2 r1, r2 := fun3(1, 2, "111") fmt.Println(r1, r2) // 函数在Go语言中本身也是一个数据类型,加了() 是调用函数,不加(), 函数也是一个变量,可以赋值给别人。 // 函数的类型就等于该函数创建的类型,他也可以赋值给}// 无参无返回值的函数func func1() {}// 有参有返回值的函数func func2(a, b int, c ...string) (int, int) { return 0, 0}
3. 函数的本质
函数在Go语言中不是一个简单的调用或者接收结果的。
函数在Go中是一个符合类型,可以看做是一个特殊的变量。var 定义吗,赋值。类型相同即可
函数类型的样子 :var f1 函数名(参数) 结果
变量名:指向一段内存 (num --> 0x11111aaaa)
函数名:指向一段函数体的内存地址,是一种特殊类型的变量。我们可以将一个函数赋值给另外一个类型相同的函数
4. 匿名函数
Go语言支持匿名函数,即没有名称的函数。匿名函数通常用于实现局部的、一次性的功能,或者作为高阶函数的参数传递。
匿名函数,在函数体后增加(),调用了这个函数,匿名函数只能一次。
将匿名函数进行赋值,就可以实现多次调用。
Go语言支持函数式编程
将匿名函数作为另外一个函数的参数,回调函数将匿名函数作为另外一个函数的返回值,可以形成闭包结构package mainimport "fmt"// 匿名变量 (没有名字的变量)// 匿名函数 (没有名字的函数)func main() { // 正常的调用 f12() f2 := f12 // 函数赋值给另外一个函数 f2() // f12 f2 本质指向了同一个内存空间,空间中的代码一致 {fmt.Println("我是f12函数")} // 匿名函数,在函数体后增加(),调用了这个函数,匿名函数只能一次。 func() { fmt.Println("我是一个匿名函数1") }() // 将匿名函数进行赋值,就可以实现多次调用。 f3 := func() { fmt.Println("我是一个匿名函数2") } //将匿名函数赋值给变量后,可以直接通过变量调用 f3() // 匿名函数是否可以添加参数和返回值 func(a, b int) { fmt.Println("a,b") }(1, 2) // 将匿名函数的返回值定义给变量。 r1 := func(a, b int) int { return a + b }(1, 2) fmt.Println(r1) // 由于Go语言中的函数是一个特殊的变量,支持匿名操作 // Go语言支持函数式编程 // - 将匿名函数作为另外一个函数的参数,回调函数 // - 将匿名函数作为另外一个函数的返回值,可以形成闭包结构}func f12() { fmt.Println("我是f12函数")}
5. 回调函数
高阶函数:可以将一个函数作为另外一个函数的参数。
fun1()
fun2(fun1)
fun1 函数作为 fun2 函数的参数
fun2函数,叫做高阶函数,接收了另外一个函数作为参数的函数
fun1函数,叫做回调函数,作为另外一个函数的参数
package mainimport "fmt"// 回调函数func main() { // 函数调用 r1 := add(1, 2) fmt.Println("r1是:", r1) // 高阶函数调用 r2 := oper(1, 2, add) fmt.Println("r2是:", r2) r3 := oper(1, 2, sub) fmt.Println("r3是:", r3) // 匿名函数 fun1 := func(a, b int) int { return a * b } r4 := oper(1, 2, fun1) // 调用匿名函数 * fmt.Println("r4是:", r4) // 能够直接传递匿名函数 r5 := oper(8, 2, func(a int, b int) int { if b == 0 { fmt.Println("除数不能为0") return 0 } return a / b }) fmt.Println("r5是:", r5)}// 运算 (运算的数字,运算操作)// 高阶函数,参数是接收另外一个函数func oper(a, b int, fun func(int, int) int) int { //打印出的fun是每次传入的函数内存地址 fmt.Println(a, b, fun) r := fun(a, b) return r}func add(a, b int) int { return a + b}func sub(a, b int) int { return a - b}
函数也可作为可变参数传参
6. 闭包
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量
并且该外层函数的返回值就是这个内层函数。
这个内层函数和外层函数的局部变量,统称为闭包结构。
局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用
package mainimport "fmt"// 是一种特殊的结构:闭包结构,违反了程序正常的生命周期。合法的使用。程序允许的一种特殊结构,变量作用域升级了。// 什么时候用闭包: js (xxxxxxx.html 引用大量的第三方库:10个js库,js库中很多变量名是冲突的)// js 很多框架都是闭包结构的,防止变量冲突,全局变量污染// 我的代码里面的变量就不会和你代码里面的变量冲突了。解决一些变量作用域冲突的问题。/*闭包结构:一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。在闭包结构中:局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用.// 由于垃圾回收期不会将闭包中的变量销毁,可能会造成内存泄漏。*/// 你的代码变量和你同事的变量冲突了,解决。 i 新建一个变量。 第三方库中的代码都是闭包结构实现的导出。var i int = 10func main() { r1 := increment() fmt.Println(r1) // 返回的是一个 increment() 内层函数,还没有执行 // -- 执行这个内层函数 // v1 := r1() fmt.Println(v1) v2 := r1() fmt.Println(v2) fmt.Println(r1()) fmt.Println(r1()) fmt.Println(r1()) // 你写的代码是对的,但是结果不对,你的变量被污染了 fmt.Println("--------------------------") // r2和r1指向同一个地址 r2 := increment() // 再次调用的时候 ,i = 0 v3 := r2() fmt.Println(v3) // 1 //因为我们内层还是用i,还存在引用,系统不会销货这个i,保护,单独作用r1 fmt.Println(r1()) // 6 // 这里的i 并没有随着 第二次创建就被销毁归0,而是在内层函数继续调用着。 fmt.Println(r2()) // 2 // r1 名字 ----> 内存地址 &r1 fmt.Printf("%p\n", &r1) fmt.Printf("%p\n", &r2)}// 自增函数// increment() 函数返回值为 func() int 类型func increment() func() int { // 外层函数,项目(很多的全局变量) // 定义一个局部变量 i := 0 // 在外层函数内部定义一个匿名函数,给变量自增并返回。 fun := func() int { i++ return i } return fun}
如果我们想使用闭包结构来解决全局变量污染的问题,那我们就可以写一个闭包结构来创建执行的函数。
通过这个闭包结构创建的函数内部的变量,都在这个函数中作用,不会和其他函数冲突。
**闭包结果的返回值是一个函数。**这个函数可以调用闭包结构中的变量。
package mainimport "fmt"// 是一种特殊的结构:闭包结构,违反了程序正常的生命周期。合法的使用。程序允许的一种特殊结构,变量作用域升级了。// 什么时候用闭包: js (xxxxxxx.html 引用大量的第三方库:10个js库,js库中很多变量名是冲突的)// js 很多框架都是闭包结构的,防止变量冲突,全局变量污染// 我的代码里面的变量就不会和你代码里面的变量冲突了。解决一些变量作用域冲突的问题。/*闭包结构:一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。在闭包结构中:局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用.// 由于垃圾回收期不会将闭包中的变量销毁,可能会造成内存泄漏。*/// 你的代码变量和你同事的变量冲突了,解决。 i 新建一个变量。 第三方库中的代码都是闭包结构实现的导出。var i int = 10func main() { r1 := increment() fmt.Println(r1) // 返回的是一个 increment() 内层函数,还没有执行。直接打印函数名得到的是名字的地址 // -- 执行这个内层函数 // v1 := r1() fmt.Println(v1) v2 := r1() fmt.Println(v2) fmt.Println(r1()) fmt.Println(r1()) fmt.Println(r1()) // 你写的代码是对的,但是结果不对,你的变量被污染了 fmt.Println("--------------------------") // r2和r1指向同一个地址 r2 := increment() // 再次调用的时候 ,i = 0 v3 := r2() fmt.Println(v3) // 1 //因为我们内层还是用i,还存在引用,系统不会销货这个i,保护,单独作用r1 fmt.Println(r1()) // 6 // 这里的i 并没有随着 第二次创建就被销毁归0,而是在内层函数继续调用着。 fmt.Println(r2()) // 2 // r1 名字 ----> 内存地址 &r1 fmt.Printf("%p\n", &r1) fmt.Printf("%p\n", &r2)}// 自增函数// increment() 函数返回值为 func() int 类型//通过increment()可以创建无数个函数,让incremnet()变量不会影响每个函数的使用func increment() func() int { // 外层函数,项目(很多的全局变量) // 定义一个局部变量 i := 0 // 在外层函数内部定义一个匿名函数,给变量自增并返回。 fun := func() int { i++ return i } return fun}
闭包计算斐波那契数
package mainimport "fmt"// 返回一个“返回类型为int的函数” ,将闭包函数作为值返回func fibonacci() func() int { a, b := 1, 1 //定义初始的两个值 return func() int { c := a a, b = b, a+b return c }}func main() { f := fibonacci() //计算前10个斐波那契数列 for i := 0; i < 10; i++ { fmt.Println(f()) }}