【GO语言04-1】Go语言中关于函数定义、闭包函数、作用域、defer关键字详解

Go语言中的功能封装是通过函数进行的,不同结构体之间可以通过接口来进行统一,再结合反射特性就可以开发大型的、复杂的项目。

一、函数的定义

Go语言是支持面向对象编程的,但是函数才是Go语言的基本组成元素。
Go语言的函数分为具名函数、匿名函数

//具名函数
func main(a int) int {
	return a * a
}
main(11)

//匿名函数
var nua = func(a int) int {
	return a * a
}
nua(12)

二、闭包

匿名函数可以赋值给一个变量,也可以直接写在另一个函数的内部:

func main() {
	f := double()
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
}

func double() func() int  {
	// 1、int类型的默认初始值是0
	var r int
	// 2、返回一个匿名函数
	return func() int {
		r++
		return r*2
	}
}

这里注意一下,返回值的类型为 func() int ,返回的是一个匿名函数
这里的匿名函数就是闭包函数
这里的输出结果:

2
4
6
8

很奇怪的是,为什么不是 2 2 2 2 呢?这就是闭包函数与普通函数的不同之处。
匿名函数在编译的时候将变量包装进自己的函数内了,从而跨过了作用域额限制,可以让变量r一直存在,这就是闭包。

补充一下:

1、闭包会把函数和所访问的变量打包到一起,不再关心这个变量原来的作用域,闭包本身可以看作是独立对象。
2、闭包函数与普通函数的最大区别就是参数不是值传递,而是引用传递,所以闭包函数可以操作自己函数以外的变量。
3、闭包函数对外部变量的操作才使其不能被释放回收,从而跨过了作用域的限制。

三、作用域

对比一下两组代码:

var funcList []func()
for i:=0; i<3; i++{
	funcList = append(funcList,func(){
		fmt.Println(i)
	})
}
for _,f := range funcList{
	f()
}

运行结果:

3
3
3
var funcList2 []func()
for i:=0;i<3;i++{
	j := i  //<<<=================== 不同之处
	funcList2 = append(funcList2,func(){
		fmt.Println(j)
	})
}
for _,f := range funcList2{
	f()
}

运行结果:

0
1
2

上述两个代码块的结果对比差异比较大,原因是在于匿名函数使用外部变量用的是指针,并非值复制。
在for循环中变量i是一个共享变量,每一次循环都会把原理存储的值加1。三次循环执行完后,i存储的值就是3。
第一个代码块中,函数执行的时候虽然打印了3次,也只是重复打印了三次3。
第二个代码块中,循环体内每次执行的时候额外声明了一个局部变量j,相当于每次都把i不同的值存储下来。

注意:
Go语言里面的局部变量名称和全局变量名称是可以重复的,重复的时候系统会默认把重名变量看作是局部变量。

	var funcList3 []func(int)
	for i:=0;i<3;i++{
		funcList3 = append(funcList3,func(i int){
			fmt.Println(i)
		})
	}
	for i,f := range funcList3{
		fmt.Println("===========",i)
		f(i)
	}

运行结果:

=========== 0
0
=========== 1
1
=========== 2
2

闭包函数会将自己用到的变量都保存在内存中,导致变量无法被及时回收,并且可能通过闭包修改父函数使用的变量值。

四、返回值与变长参数

4.1、返回多个值

func swap(a,b int) (int,int) {
	return b,a
}
fmt.Println(swap(1,2))

4.2、入参为变长参数

func sum(a int,others ...int) int {
	for _,v := range others{
		a += v
	}
	return a
}
fmt.Println(sum(1,2,3,4,5,6,7,8,9,10)) // 55

argus := []int{2,3,4,5,6,7,8,9,10}
// 加...把切片里面的值解析出来再传递
fmt.Println(sum(1,argus...)) // 55

4.3、空接口作为变长参数

func print(a ...interface{})  {
	fmt.Println(a...)
}
// 空接口定义可变长参数
args := []interface{}{1234,"abcd"}
print(args) // [1234 abcd]
print(args...) // 1234 abcd  加 ... 把切片里面的值解析出来再传递

Go语言中函数的参数传递只有值传递一种方式,即便是使用指针,也是使用值传递的方式传递了指针的值。

这里要强调一下函数的参数传递方式。Go语言中,函数的参数只能进行值传递,不能进行引用传递。虽然可以使用指针,但是本质上传递的还是指针指向的地址,因为访问的是地址内的值,所以会被误认为是引用传递。

比如,切片会让人觉得函数在处理切片时使用的是引用传递,其实是因为切片里面包含地址,所以可以直接访问。

此外,切片包含的长度和容量也是通过值传递传到函数内的,如果在函数内修改了长度或容量,函数外的切片是接收不到的,所以需要再返回一个切片,也是基于这个原因,append函数才会每次都返回切片。

五、defer关键字

在实际应用中,确保在执行函数的过程中遇到报错时能及时处理一些必要的事情,比如关闭连接等情况可以使用defer关键字来实现这些功能。

defer关键字用于释放资源,会在函数返回之前调用,即便函数崩溃也会在结束前调用defer。

一般用法如下:

f,err := os.Open(fileName)
if err != nil{
	panic(err)
}
defer f.Close()

这样操作后,后面处理的代码即便报错,也会在结束前先执行文件关闭操作。

一个函数内也可以有多个defer,在调用的时候按照栈的方式先进后出,即写在前面的会后调用(由最里层的defer语句先执行)。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
三个目的:1.go语言的基础彻底讲明白,所以我们要100讲2.课程持续更新-随着go的新版本、新特性 持续更新3.我们的文档希望能成为go语言的查询文档 目标:让大家尽快完整的掌握go基础 尽快为什么尽快,因为我看到过很多人的半途而费,这里面有毅力的成分但是其实我更想说,市面上的培训千篇一律,大家都在变着花的讲基础,把一个简单的事情用各种各样的不常用的方式讲述出来我认为这会伤害一个人的学习的积极性学习有一个很重要的一条规则是: 尽快的得到正反馈所以前几十节,我只会告诉你最实用的东西,目标只有一个,让你可以思路清晰的先把go用起来,得到正反馈,激发你对他的热爱课时会保持在20分钟以内,如果你真想在地铁上学习,可以选择看一节  完整我们的基础课程之所以做100讲,就是要把go语言基础讲清楚 比如我,10年专注于开发、架构,前百度资深开发工程师现任某金融集团技术总监,丰富的开发经验,熟悉go、python开发语言我们每天都在用go解决各种各样的问题,我们深度使用了prometheus、生产环境深度使用了K8s、Docker等我们也致力于用go完成所有工作  go语言100讲之后的课程规划我们会用去go围绕go生态k8s、prometheus去围绕redis、kafka、nacos、ftp、elk等一系列工具我们的目标是用go完成我们所遇到的绝大部分工作,所以我们不写类似于聊天室这种项目我们是一群开发人员、运维人开发员组成的团队,有架构师、程序员、资深运维开发、DBA、应用运维、运维总监、网络工程师等,几乎包括了生态的各个方面因为我们想做我们专长的事情

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值