函数
函数是go语言中的一等公民。
在go中一个函数的声明可以出现在它的调用之前,也可以出现在它的调用之后。
当然,在java中我们一般称呼函数为方法。在函数定义语法上,go和java还是有很多区别点的。
普通函数
一个简单例子
1 | func SquareSumAndDiff(a int64, b int64) (c int64, d int64) { |
这一个简单的例子就有很多可说的。
go的参数声明都是名字在前,类型在后。函数名首字母大小写规则遵循导出规则。
多个参数,如果类型一样,可以简写
(a, b int64)。另外go支持变长参数,在类型前加...go支持多返回值。这个确实方便。习惯了之后,会觉得java的单返回值写起来有点麻烦。
如果只有一个返回值,并且返回值没有名字,那么可以不写括号()。但是入参的括号永远不能省略。
仔细看例子,返回值是可以有名字的。这个名字相当于进行了变量申明,在函数中可以直接使用。
在这种返回值有名字的情况下,return c, d可以简写为return。如果返回值是匿名的,那么必须显示return c,d
1
2
3
4
5
6func SquareSumAndDiff2(a int64, b int64) (int64, int64) {
x, y := a+b, a-b
c := x * x
d := y * y
return c, d
}
关于函数参数
go函数传参采用的是值传递的方式。就是将实际参数在内存中一个bit一个bit的拷贝到形参里,比如传整型,数组或者string类型,即使这个数组很大,也是copy整个原始数组传进去。
但是像切片,map,struct等类型,他们的内存存储的是具体内容的引用(或者叫指针)。这样传参时copy的也是引用。
关于多返回值
返回值是可以有名字的,称为具名返回值。
Go 标准库以及大多数项目代码中的函数,都是使用普通的非具名返回值形式
匿名函数
首先要明确的一个事情是:匿名函数的定义不是一种函数声明。一个标准的函数只能声明在包级。不能在一个函数中再声明新的函数。
但是可以在一个函数中定义一个匿名函数。
1 | package main |
闭包
上面例子中的最后一个匿名函数处于变量x和y的作用域内,所以在在它的函数体内可以直接使用这两个变量。 这样的函数称为闭包(closure)。
事实上,Go中的所有的自定义函数(包括普通函数和匿名函数)都可以被视为闭包。
换句话说,所谓的闭包,不过是一个函数可以使用函数外定义的变量。
一个很常见的闭包的例子就是一个函数的返回值是函数
1 | func incr() func() int { |
函数incr()的返回值是一个函数。
1 | func TestClosure(t *testing.T) { |
可以看到,多次调用闭包函数。x都增加了。也就是说x并没有随着函数的结束而销毁。因为x已经被变量f持有,只要f还在其作用域内,x也就一直存在下来了。通过x的增加,也可说明f对x的持有是保存其地址,所以x始终是同一个x。
1 | t.Log(incr()()) // 1 |
这样写,每次都是一个新的x。所以一直是1
init函数
init函数的作用是在main执行前进行一些初始化工作。
每个代码包下,可以有多个init函数。init函数和main函数一样,没有入参,没有返回值。
1 | func init() { |
init函数是在包加载的时候执行。
多个init按照声明的顺序串行执行。
一个代码包依赖于应一个代码包,那么一定是被依赖的代码包的init先执行。
在加载一个代码包的时候,此代码包中声明的所有包级变量都将在此包中的任何一个init函数执行之前初始化完毕。
defer函数
go的函数内部可以使用defer关键字,产生类似java的try-catch-finally的finally效果。
但是,go的defer函数可以写多个。最后的执行顺序和声明顺序使相反的。可想而知,是用栈结构来存的多个defer函数。
1 | package main |
panic恢复
go没有java的异常抛出机制。go产生一个运行时异常称为发生了panic(恐慌)。如果这个panic没有处理,将导致整个应用的崩溃。
消除panic,可以在defer中调用recover函数,这是go内置的函数。
1 | package main |
1 | 嗨! |
无论在哪个 Goroutine 中发生未被恢复的 panic,整个程序都将崩溃退出。
一等公民
我们说函数在go语言中是一等公民。怎么理解这句话?
首先什么叫一等公民?这个业界还没有公认的准确定义,引用wiki 发明人的解释
如果一门编程语言对某种语言元素的创建和使用没有限制,我们可以像对待值(value) 一样对待这种语法元素,那么我们就称这种语法元素是这门编程语言的“一等公民”。 拥有“一等公民”待遇的语法元素可以存储在变量中,可以作为参数传递给函数,可以 在函数内部创建并可以作为返回值从函数返回。
也就是说一门语言的一等公民元素,其创建和使用非常灵活,可以用在各个地方。那我们对比下go的函数
存储在变量里
类似前面匿名函数的例子
1
2
3
4
5
6func TestFuncAsParam(t *testing.T) {
var f = func() string {
return "hello"
}
t.Log(f())
}函数作为返回值
1
2
3
4
5
6
7
8
9
10func TestFuncAsReturn(t *testing.T) {
var f1 = func() func() string {
return func() string{
return "a func"
}
}
var f2 = f1()
t.Logf("%T", f1())
t.Log(f2())
}匿名函数的返回值还是一个函数,保存到变量f1
作为参数传到方法里
1
2
3
4
5
6
7func TestTime(t *testing.T) {
time.AfterFunc(time.Second * 2, func() {
println("after 2 second...")
})
time.Sleep(time.Second * 3)
t.Log("end")
}比如time包的AfterFunc函数就可以接收一个函数作为参数
函数有自己的类型,函数类型
func关键字+函数的参数列表+返回值列表就是一个函数类型。
其中函数的参数列表+返回值列表也成为函数签名
不用管参数或者返回值的名字。无所谓。
函数签名一样的两个函数就是同一个函数类型
因为函数有类型,也就是说函数可以进行显式的类型转换
函数拥有以上众多特性,可以称为go语言的一等公民