函数
前言
在go语言中,函数是“一等公民”,即函数可以当普通变量使用,可以实现匿名函数、被当作返回值、将函数作为另一个函数的参数。 go不支持给函数参数设置默认值。
函数的结构
Go语言中函数的基本定义语法:
func 函数名([参数列表]) [函数返回值类型] {
函数体
}
- 参数列表限定了函数调用的时候可以传入的实参个数、顺序以及类型
- 函数如果有返回值,就需要指定返回值的数据类型;如果不指定,则表示函数不返回任何值。
- Go语言不支持函数重载,同一个包中不允许定义同名的函数,即便参数不同也不行。内置的
init
函数除外。
示例代码:
package main
import "fmt"
func sum(a int,b int) int {
return a + b
}
// 参数类型相同时,可以如此简写
func sum2(a,b int) int {
return a + b
}
// 直接定义返回值的变量名,函数体内就无需再声明,且return时候可直接返回
func sum3(a,b int) (sum int) {
sum = a + b
return
}
// 多个返回值,返回error
func div(a,b int) (result int, err error) {
if b == 0 {
err = errors.New("除数不能为0")
return
}
result = a / b
return
}
func main() {
c := sum(2,3)
fmt.Printf("sum(2,3)=%d \n",c)
}
函数返回函数
package main
import "fmt"
func sum(a int,b int) func(int,int) int {
return func(c int,d int) int {
return a+b+c+d
}
}
func main() {
c := sum(2,3)(4,5)
fmt.Printf("sum(2,3)(4,5)=%d \n",c)
}
- 示例代码的函数体用
return
返回了一个接收两个整数的函数(匿名函数)。return
的函数签名必须和sum()
函数返回值类型相同。
函数返回多个值
在调用具有多个返回值的函数时,如果要将函数返回值赋值给变量,那么变量的个数和数据类型必须和函数返回值的个数和数据类型相同。
匿名函数
如果一个函数只需要调用一次,功能也不复杂,那么就可以使用匿名函数。
- 匿名函数即时调用示例代码:
package main
import "fmt"
func main() {
s, m := func (a int,b int) (int,int) {
return a+b,a*b
}(2,3)
fmt.Printf("sum(2,3)=%d \n",c)
}
- 也可以将匿名函数赋给一个变量,之后就可以通过这个变量多次调用匿名函数。
package main
import "fmt"
func main(){
f := func(a int,b int) (int,int) {
return a+b,a*b
}
s,m := f(2,3)
// 匿名函数(2,3)=5,6
fmt.Printf("f(2,3)=%d,%d\n",s,m)
s,m = f(3,7)
fmt.Printf("f(3,7)=%d,%d\n",s,m)
}
变长函数
Go语言中的变长函数即不定参数,通过特殊的函数参数——变长参数,实现参数不定。变长参数定义时需要在数据类型前面用三个点...
来指定。例如:...int
package main
import "fmt"
// ns 不定参数
func sum(ns ...int) int {
ret := 0
for _, n:= range ns {
ret += n
}
return ret
}
func main() {
fmt.Println(sum())
fmt.Println(sum(1))
fmt.Println(sum(2,3))
fmt.Println(sum(2,3,4))
}
递归函数
- 当函数直接或间接调用函数自身,则该函数被称为递归。
- 递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有退出条件,否则会进入无限循环而无法退出。
- 函数调用一般是通过栈来实现的,如果递归调用次数过多,就可能会导致栈内存溢出的情况。
- 递归中不保存临时运算结果,因此计算速度比较慢。递归层次越多,计算速度越慢。
package main
import "fmt"
// 使用递归函数求解斐波拉契数列
func fib(n uint64) uint64 {
if n <= 1 {
return n
}
return fib(n-2) + fib(n-1)
}
func main() {
var i uint64 = 10
fmt.Printf("fib(8)= %d\n", fib(i))
}
回调函数
- 回调函数的本质就是作为另一个函数参数的函数。在函数体中,在适当的时机调用参数对应的函数,形成回调。
package main
import "fmt"
// 定义一个callback类型
// 这个callbacl类型是一个函数,签名为func(int,int) int
type callback func(int,int) int
func doAdd(a,b int,f callback) int {
fmt.Println("f callback")
return f(a,b)
}
func add(a,b int) int {
fmt.Println("add running")
return a + b
}
func main() {
a,b := 2,3
fmt.Println(doAdd(a,b,add))
fmt.Println(doAdd(a,b,func(a int,b int) int {
fmt.Println("==================")
return a*b
}))
}
闭包
没有闭包功能,函数执行完毕后就无法再修改函数中变量的值;有了闭包后,函数就是一个变量的值,只要这个变量没有被释放,就可以在后期修改函数中变量的值。
package main
import "fmt"
// 闭包实现的累加器函数
func adder(i int) func(int) int {
ret := i
return func(n int) int {
ret += n
return ret
}
}
func main() {
fc := adder(2)
// 2 + 1+2+3 -> 8
fc(1)
fc(2)
fc(3)
// 13=8+5
fmt.Println(fc(5))
// 20=13+7
fmt.Println(fc(7))
}
defer关键字
在python中常见try ... except ... finally ...
语句,go语言设计者认为这种方式会产生大量的嵌套,并且不够简洁,因此设计出defer
关键字替代finally
,并设计不同的错误处理机制。
defer
是Go语言提供的一种用于延迟调用的机制,常用于对资源进行释放的场景,比如释放数据库连接、解锁和关闭文件等。
defer
之后只能是函数调用,不能是表达式。
当多个defer
语句被定义时,他们会以定义的逆序依次执行。
package main
import "fmt"
func print(s string) {
fmt.Println("run ",s)
}
func main(){
fmt.Println("=======start=======")
// defer执行顺序和调用顺序相反
defer print("order 1")
defer print("order 2")
defer print("order 3")
fmt.Println("=======end=======")
}
假设先定义了一个函数func1
,通过defer
去延迟调用,在defer
之后对func1
进行了修改,那么defer
执行的是修改前的func1
,而不是修改后的func1
,因为defer
是一种压栈操作。
// 这段代码的输出结果是10,而不是11
// defer会连函数参数的值一起拷贝压栈
func deferTest1() {
x := 10
defer func (a int) {
fmt.Println(a)
}(x)
x++
}
// 但如果传的是指针,那么结果是11
func deferTest2() {
x := 10
defer func (a *int) {
fmt.Println(a)
}(&x)
x++
}
//defer的函数不传参数,函数内部去找函数作用域之外的变量,因此结果还是11
func deferTest3() {
x := 10
defer func () {
fmt.Println(x)
}()
x++
}
变量逃逸分析
# -l: 禁用函数的内联功能,这样能更好地观察逃逸情况,减少干扰
# -m: 打印出逃逸分析的优化策略,可以同时使用多个,比如 -m -m
go build -gcflags "-l -m" ./main.go
函数健壮性“三不要原则”
- 不要相信任何外部输入的参数。函数应该对所有输入的参数进行合法性检查,一旦出现问题,立即终止函数的运行,返回预设的错误。
- 不要忽略任何一个错误,尤其是生产环境。不能假定函数调用一定会成功,一定要显式检查这些调用返回的错误值。一旦发现错误,要及时中止函数的运行,防止错误继续传播。
- 不要假定异常不会发生