函数定义
Go语言中定义函数使用func关键字,具体格式如下:
func 函数名(参数)(返回值){函数体}
函数参数
- 类型简写
函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:
func intSum(x, y int) int {return x + y}
- 可变参数
和java一样,Go语言中的可变参数通过在参数名后加…来标识。
func intSum2(x ...int) int {fmt.Println(x)sum := 0for _, v := range x {sum = sum + v}return sum}
在函数,可变参数需要当成是一个切片来处理
函数返回值
Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。
func calc(x, y int) (int, int) {sum := x + ysub := x - yreturn sum, sub}
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。
func calc(x, y int) (sum int, sub int) {sum := x + ysub := x - yreturn}
这看起来感觉没有什么意义,再看下面代码:
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 calculationcal = addcal(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 addcase "-":return subdefault: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 intreturn func(y int) int {sum += yreturn sum}}func main() {f1 := addup()fmt.Println(f1(10)) //10fmt.Println(f1(20)) //30fmt.Println(f1(30)) //60f2 := addup()fmt.Println(f2(40)) //40fmt.Println(f2(50)) //90fmt.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 += ireturn base}sub := func(i int) int {base -= ireturn base}return add, sub}func main() {f1, f2 := calc(10)fmt.Println(f1(1), f2(2)) //11 9fmt.Println(f1(3), f2(4)) //12 8fmt.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")}输出结果为:startend321
由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等
go函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。如下图:
看一个打脑壳的面试题:
func calc(index string, a, b int) int {ret := a + bfmt.Println(index, a, b, ret)return ret}func main() {x := 1y := 2defer calc("AA", x, calc("A", x, y))x = 10defer 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 Arecover in Afunc B
函数init
每个包可以包含任意多个init 函数,这些函数都会在程序执行开始的时候被调用。所有被编译器发现的init 函数都会安排在main 函数之前执行。init 函数用在设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。
