名人说:莫愁千里路,自有到来风。 ——钱珝
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)目录
- 1、本质、数据类型与延迟函数
- ①函数的本质
- ②函数的数据类型
- ③defer延迟函数
- 2、匿名、回调函数与闭包
- ①匿名函数
- ②回调函数
- ③闭包
- 3、小结
上一篇博客中主要了解了函数的概念、定义、参数以及递归函数等内容,在本篇博客中,我们将接着探讨Go语言中的一些函数概念,包括函数的本质、数据类型、延迟执行函数、匿名函数、回调函数以及闭包。内容上,将分为两大部分来陈述,每一部分不仅会介绍相关的理论知识,还会通过语法格式和完整的代码案例来帮助大家更好地理解。
1、本质、数据类型与延迟函数
①函数的本质
在Go语言中,函数更加强大,可以像其他类型一样被赋值给变量,可以作为参数传递给其他函数,也可以作为其他函数的返回值。所以函数的本质在这里你可以看做是具有一定功能的代码块,也可以看做一种变量。这些函数设定大大增强了Go语言的灵活性和表达能力。
语法格式:
func 函数名(参数列表) (返回值列表) {// 函数体
}
接下来让我们看一个案例,回顾一下上篇内容所学的函数定义及调用,同时也能从案例中再次思忖函数的本质。
案例1:定义并调用简单函数
package mainimport "fmt"//求和函数
func sum(a int, b int) int {return a + b
}//主函数
func main() {result := sum(5, 7)fmt.Println("5 + 7 =", result)
}
从上面可以看出,其实这些函数都具有一定功能,而且用花括号{}包裹了起来,比如main函数,程序的入口,就好比树干,人的大脑,而自定义函数类似于树枝,人的各个器官。
接下来咱们再看一个案例,你可以发现函数在go中简直就是测量仪器中的万用表。
案例2:《论函数的多样性》
package mainimport "fmt"func main() {fmt.Printf("%T\n", testF)var f5 func(int, int)f5 = testF //此处赋值的是testF函数的引用,即f5可直接引用testF,因此二者指向同一块地址fmt.Println(f5)fmt.Println(testF)f5(1, 2)
}//func() 本身就是一个数据类型
//func如果不加(),函数本质就是一个变量func testF(a, b int) {fmt.Println(a, b)
}
②函数的数据类型
在Go语言中,函数也有自己的类型,这个类型由函数的签名决定,包括参数的类型和返回值的类型。
语法格式:
定义一个函数类型:
type 函数类型名 func(参数类型列表) 返回值类型列表
案例:函数类型
package mainimport "fmt"// 定义一个函数类型
type operation func(int, int) int// 实现加法函数
func add(a int, b int) int {return a + b
}func main() {var op operation = addresult := op(3, 4)fmt.Println("3 + 4 =", result)
}
关于这句代码"var op operation = add",在当时看到的时候有些懵,这是啥,仔细捋一捋,才弄明白。首先var op operation = add
,var来声明变量的,operation是自定义的函数类型,再考虑到之前var a int
的意思是定义声明a为变量且为int类型,那这就好理解了,var op operation
与之类似,意思是定义声明op为变量且为operation类型,而operation则是一个函数类型,所以精简下来就是,定义声明op为变量且为函数类型,并将add赋值给op。
因此这行代码执行后,op
变量实际上就是一个函数,具体到这个例子中,它是add
函数的一个引用或者说是别名。
你可以通过调用op
来执行add
函数,如op(3, 4)
会调用add
函数,传入3和4作为参数,执行加法操作,并返回结果7。
③defer延迟函数
defer
关键字用于延迟一个函数或者方法的执行。defer
语句会将其后面跟随的函数调用推迟到包含该defer
语句的函数执行完毕时执行。
语法格式:
defer 函数调用
案例1:使用defer语句,延迟hello world
package mainimport "fmt"func main() {defer fmt.Println("world")fmt.Println("hello")
}
通过延迟函数可以很神奇的发现,放在前面的world居然居后输出了,思来想去,不得其解,这究竟原因在何呢?通过查询一番,找到了答案:
Go语言为每个goroutine维护了一个延迟调用栈。当执行到一个defer
语句时,后面的函数调用不会立即执行,而是被放入到当前goroutine的延迟调用栈中。这些被延迟的函数调用会按照后进先出(LIFO)的顺序执行,也就是说,最后被延迟的函数会最先执行。
一看到栈,很多小伙伴可能与俺一样能够明白其中一二了,那这样做又有什么优势呢?(没用加它干嘛,还得学,学不完的知识ing)
原来,这种基于栈的延迟调用机制能让资源管理变得简单而直观。咱们开发者不必担心资源释放的具体时机,只需在获取资源后立即通过defer
来定义释放资源的逻辑。
巴拉巴拉一大堆…
总之,这样可以减少资源泄露的风险,同时使代码更加清晰和易于维护。
接下来再看一个案例,巩固一下所学概念:
案例2:使用defer语句,延迟自增
package mainimport "fmt"func main() {a := 10fmt.Println("a=", a)//go语言中,使用defer关键字来延迟一个函数或方法的执行defer f(a) //由于defer的影响,函数在最后执行,但参数此时已经传递进去,因此最后的值仍为10a++fmt.Println("end a=", a)
}func f(s int) {fmt.Println("函数里面的a的值s为:", s)
}
2、匿名、回调函数与闭包
①匿名函数
匿名函数是没有名称的函数(就好比论坛里的匿名发言,问卷里的匿名表单),可以在需要函数类型的地方直接定义和使用。
语法格式:
func(参数列表) (返回值列表) {// 函数体
}
案例1:使用匿名函数
package mainimport "fmt"func main() {sum := func(a int, b int) int {return a + b}fmt.Println(sum(3, 4)) // 输出:7
}
案例2:函数式编程
package mainimport "fmt"func main() {test1()//函数本身也可以看做是一个变量f2 := test1f2()//匿名函数f3 := func() {fmt.Println("我是f3函数")}f3()//匿名函数自己调用自己func() {fmt.Println("我是f4函数")}()//匿名函数自己调用自己,可以有参数的func(a, b int) {fmt.Println(a, b)fmt.Println("我是f5函数")}(1, 2)//参数+返回值r1 := func(a, b int) int {fmt.Println("我是f5函数")return a + b}(1, 2)fmt.Println(r1)
}//go语言支持函数式编程:
//1、将匿名函数作为另外一个函数的参数,回调函数
//2、将匿名函数作为另外一个函数的返回值,可以形成闭包结构func test1() {fmt.Println("我是f2函数")
}
②回调函数
回调函数是作为参数传递给其他函数的函数,这允许一种灵活的方式来传入代码,实现类似插件的功能。
语法格式:
func 函数名(参数列表, 回调函数参数) (返回值列表) {// 函数体中调用回调函数
}
案例1:使用回调函数
package mainimport "fmt"func calculate(a int, b int, operation func(int, int) int) int {return operation(a, b)
}func main() {add := func(a int, b int) int {return a + b}result := calculate(5, 3, add)fmt.Println("5 + 3 =", result)
}
案例2:高阶函数与回调函数
//创作者:Code_流苏(CSDN)
package mainimport "fmt"// 高阶函数和回调函数、// 高阶函数
// 指至少满足下列一种条件的函数:
// 接受一个或多个函数作为参数。
// 返回一个函数。// 回调函数
// 回调函数是被作为参数传递到另一个函数中,并且在那个函数内部被调用以完成某种任务或行为的函数。
// 回调是一种实现异步编程的技巧,允许某个操作的完成时能够通知或影响另一个函数的执行。
func main() {r1 := add1(1, 2)fmt.Println(r1) //1 + 2 = 3r2 := oper(3, 4, add1)fmt.Println(r2) //3 + 4 = 7r3 := oper(8, 4, sub)fmt.Println(r3) //8 - 4 = 4r4 := oper(8, 4, func(a int, b int) int {if b == 0 {fmt.Println("除数不能为0")return 0}return a / b})fmt.Println(r4) //8 / 4 = 2
}// 高阶函数 可以接受一个函数或多个函数作为参数
func oper(a, b int, fun func(int, int) int) int {r := fun(a, b)return r
}func add1(a, b int) int {return a + b
}func sub(a, b int) int {return a - b
}
③闭包
闭包是一个函数值,它引用了函数体之外的变量。这种机制使得函数可以访问并操作函数外部的变量。这个函数可以访问和赋值这些外部变量,并且即使外部函数的执行已经完成,闭包仍然可以访问这些变量。这使得闭包成为实现数据封装和管理状态的强大工具。特点如下:
- **函数:在 Go 语言中,函数可以被当作变量处理,可以作为参数传递,也可以作为返回值。
- 词法作用域:闭包函数能够引用定义它的函数中的变量。
- 状态封装:闭包允许封装状态。即使定义它的函数已经返回,闭包仍然能够操作这些状态。
语法格式:
func 外部函数名(外部函数参数) func(内部函数参数列表) 返回值类型 {// 外部函数体return func(内部函数参数列表) 返回值类型 {// 内部函数体}
}
案例1:使用闭包实现累加器
package mainimport "fmt"func accumulator(value int) func(int) int {return func(number int) int {value += numberreturn value}
}func main() {acc := accumulator(10)fmt.Println(acc(5)) // 10 + 5 = 15 故输出:15fmt.Println(acc(10)) // 15 + 10 = 25 因此输出:25
}
案例2:计数
package mainimport "fmt"// 创建一个返回闭包的函数
func counter() func() int {count := 0// 闭包函数return func() int {count += 1return count}
}func main() {myCounter := counter()fmt.Println(myCounter()) // 输出: 1fmt.Println(myCounter()) // 输出: 2anotherCounter := counter()fmt.Println(anotherCounter()) // 输出: 1fmt.Println(anotherCounter()) // 输出: 2
}
在这个例子中,counter
函数返回了一个闭包,这个闭包操作外部函数中的 count
变量。即使 counter
函数的执行完成后,这些闭包仍然能够访问并修改 count
变量。不过,要注意,每次调用 counter
时,都会创建一个新的 count
变量和新的闭包,因此 myCounter
和 anotherCounter
操作的是不同的 count
变量。
案例3:自增
//创作者:Code_流苏(CSDN)
package mainimport "fmt"// 在 Go 语言中,闭包(Closure)是一种特殊的函数;
// 它可以捕获其定义时所在作用域中的变量。闭包是由函数和与其相关的引用环境组合而成的实体。
// 简单来说,闭包允许你在一个函数内部定义另一个函数,并且这个内部函数可以访问外部函数的变量。
func main() {r1 := increment()fmt.Println(r1)v1 := r1()fmt.Println(v1) //1v2 := r1()fmt.Println(v2) //2fmt.Println(r1()) //3fmt.Println(r1()) //4fmt.Println(r1()) //5//由于闭包结构的出现,本应该销毁的r1中的临时局部变量并未被销毁掉,//所以下面输出r1时,值在原来5的基础上自增1,变为了6r2 := increment()v3 := r2()fmt.Println(v3) //1fmt.Println(r1()) //6fmt.Println(r2()) //2
}// 自增函数
func increment() func() int {//局部变量ii := 0//定义一个匿名函数,给变量自增并返回fun := func() int { //内层函数,没有执行的//局部变量的生命周期发生了变化i++return i}return fun
}
3、小结
- 函数的本质是什么?一种灵活的、能够被当作数据处理的行为单元(变量、参数等等),函数式编程的理念
- 函数的数据类型,函数类型 func_type
- 延迟函数,原理,延迟调用栈
- 匿名函数,无名氏,但确实实实在在的函数,俺是一块砖,哪里需要哪里搬
- 回调函数,是函数,也是参数,作为参数传递
- 闭包,函数式编程,匿名函数实现,闭包被调用时,即使其外部函数已经返回,闭包仍然可以访问和操作这些捕获的变量。(跨越了平常的生命周期(通常,在一个函数执行结束后,其局部变量的生命周期也随之结束))
以上就是Go语言中关于函数的本质、数据类型、延迟执行函数、匿名函数、回调函数以及闭包的讲解和案例,本篇内容就到这里,后续学习记录会陆续更新ing…
很感谢你能看到这里,如有相关疑问,还请下方评论留言。
Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)
希望本篇内容能对大家有所帮助,如果大家喜欢的话,请动动手点个赞和关注吧,非常感谢你们的支持!