函数定义
Go语言中定义函数使用func关键字,具体格式如下:
func 函数名(参数)(返回值){
函数体
}
函数参数
- 类型简写
函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:
func intSum(x, y int) int {
return x + y
}
- 可变参数
和java一样,Go语言中的可变参数通过在参数名后加…来标识。
func intSum2(x ...int) int {
fmt.Println(x)
sum := 0
for _, v := range x {
sum = sum + v
}
return sum
}
在函数,可变参数需要当成是一个切片来处理
函数返回值
Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。
func calc(x, y int) (int, int) {
sum := x + y
sub := x - y
return sum, sub
}
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。
func calc(x, y int) (sum int, sub int) {
sum := x + y
sub := x - y
return
}
这看起来感觉没有什么意义,再看下面代码:
func f() (x int) {
defer func() {
x++
}()
return 5
}
关于defer的用法在后续会讲到,大体作用就是在返回之前调用,这里对返回值进行命名后就能在defer中直接使用,调用f返回值是6。
函数类型和函数变量
使用type关键字来定义一个函数类型
type calculation func(int, int) int
上面语句定义了一个calculation类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。
函数变量可以和普通变量一样,作为函数的参数,返回值,或赋值。如下代码:
func add(x, y int) int {
return x + y
}
var cal calculation
cal = add
cal(1,2) //返回值为3
函数变量类似C中的函数指针,有了函数变量后,就能把函数作为参数传递到函数内部执行。
- 函数变量作为参数
//定义函数变量
type calculation func(int, int) int
//定义calc函数,其中一个参数是calculation类型
func calc(x, y int, op calculation) int {
return op(x, y)
}
//或者使用匿名函数
func calc(x, y int, op func(int, int) int) int {
return op(x, y)
}
func main() {
ret2 := calc(10, 20, add)
fmt.Println(ret2) //30
}
- 函数变量作为返回值
//定义函数变量
type calculation func(int, int) int
//加法
func add(x, y int) int {
return x + y
}
//减法
func sub(x, y int) int {
return x - y
}
//根据运算符号返回函数
func getCal(symbol string) calculation {
switch symbol {
case "+":
return add
case "-":
return sub
default:
return nil
}
}
func main() {
result := getCal("+")(1,2)
fmt.Println(result)//3
}
匿名函数
函数内部也可以定义函数,但是只能定义匿名函数,匿名函数格式如下:
func(参数)(返回值){
函数体
}
我们可以运行匿名函数对上面的getCal函数进行改造
func getCal(symbol string) calculation {
switch symbol {
case "+":
return func(x, y int) int {
return x + y
}
case "-":
return func(x, y int) int {
return x - y
}
default:
return nil
}
}
闭包
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。看如下例子:
//闭包实现累加
func addup() func(int) int {
var sum int
return func(y int) int {
sum += y
return sum
}
}
func main() {
f1 := addup()
fmt.Println(f1(10)) //10
fmt.Println(f1(20)) //30
fmt.Println(f1(30)) //60
f2 := addup()
fmt.Println(f2(40)) //40
fmt.Println(f2(50)) //90
fmt.Println(f2(60)) //150
}
当调用addup()返回f1的时候,f1实际是引用了外部作用域的一个sum变量,此时sum的初始值为0.在f1的生命周期内,变量sum也一直有效。其中f1和f2引用的sum变量是不同的。
再看个例子:
func calc(base int) (func(int) int, func(int) int) {
add := func(i int) int {
base += i
return base
}
sub := func(i int) int {
base -= i
return base
}
return add, sub
}
func main() {
f1, f2 := calc(10)
fmt.Println(f1(1), f2(2)) //11 9
fmt.Println(f1(3), f2(4)) //12 8
fmt.Println(f1(5), f2(6)) //13 7
}
f1和f2都引用了base变量,所以f1和f2的调用是会互相影响的,因为他们使用的是同一个base的值。
defer关键字
go中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的主体函数即将返回时,将延迟处理的语句按defer定义的逆序
进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。例如:
func main() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
}
输出结果为:
start
end
3
2
1
由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等
go函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。如下图:
看一个打脑壳的面试题:
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
答案我先不贴出来,以后看到可以先思考下。提示一点:defer的作用域
panic和recover实现异常机制
panic可以理解成java中的throw,当执行到panic的时候会使程序异常退出。这个时候就要用recover来捕捉到panic,可以理解成java中的catch。有点需要注意,recover只有在defer调用的函数中有效。
看如下例子
func funcA() {
defer func() {
err := recover()
//如果程序出出现了panic错误,可以通过recover恢复过来
if err != nil {
fmt.Println(err)
fmt.Println("recover in A")
}
}()
panic("panic in A")
}
func funcB() {
fmt.Println("func B")
}
func main() {
funcA()
funcB()
}
输出结果为:
panic in A
recover in A
func B
函数init
每个包可以包含任意多个init 函数,这些函数都会在程序执行开始的时候被调用。所有被编译器发现的init 函数都会安排在main 函数之前执行。init 函数用在设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。