func
go 函数不接受缺省参数
go 函数不能重载
函数可以返回多个值
如果没有返回值,那么就直接省略最后的返回信息。如果有返回值, 那么必须在函数的外层添加 return 语句
func 声明
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
//这里是处理逻辑代码
//返回多个值
return value1, value2
}
多个返回值
package main
import (
"fmt"
)
func max_and_min(a, b int) (int, int) {
if a > b {
return a, b
} else {
return b, a
}
}
func main() {
a, b := 1, 2
a, b = max_and_min(a, b)
fmt.Println(a, b) //2 1
}
可变参数
package main
import (
"fmt"
)
func max(arg ...int) int {
//在函数体中, 变量 arg 是一个 int 的 slice
max := arg[0]
for _, x := range arg {
if x > max {
max = x
}
}
return max
}
func main() {
fmt.Println(max(1, 2, 3, 4, -1)) //4
}
传值和传指针
go 语言中没有 c++ 中的引用传递,若要改变同一个对象则需要显示的指针传参(大部分类型)
go 语言中 channel,slice,map 这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变 slice 的长度,则仍需要取地址传递指针)
defer
defer 语句在函数返回前或者 panic 前执行,多条 defer 语句则以先进后出的顺序执行
package main
import (
"fmt"
)
func gg() {
defer fmt.Println("defer") //在函数返回前或者 panic 前执行
a := "xxx"
fmt.Println(a)
}
func aa() {
for i := 1; i < 5; i++ {
defer fmt.Println(i) //后进先出的顺序执行
}
}
func main() {
gg()
aa()
}
//xxx
//defer
//4
//3
//2
//1
函数作为值、类型
声明函数类型
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
函数当做值和类型能让程序更加灵活, 减少代码冗余
package main
import (
"fmt"
)
//声明一个函数类型
type testInt func(int) bool
func isOdd(x int) bool {
if x%2 == 0 {
return false
}
return true
}
func isEven(x int) bool {
if x%2 == 0 {
return true
}
return false
}
func filter(slice []int, t testInt) []int {
var result []int
for _, v := range slice {
if t(v) {
result = append(result, v)
}
}
return result
}
func main() {
a := []int{1, 2, 3, 4, 5, 6}
fmt.Println(filter(a, isOdd)) //[1 3 5]
fmt.Println(filter(a, isEven)) //[2 4 6]
}
recover 和 panic
go 不能抛出异常,而是使用了 panic 和 recover 机制。但是 go 语言建议尽量不要使用 panic
panic 是一个内建函数,可以中断函数原有的控制流程(defer 语句仍然会执行完), 若 panic 在本层函数中没有被 recover 捕获则向上层函数传递,直到遇到 defer 中的 recover ,否则整个进程终止。panic 可以由除 0 等运行错误产生,也可以直接调用
recover 是一个内建函数,在正常的执行过程中,调用 recover 会返回 nil,且不会产生其他任何效果。而遇到 panic 时调用 recover 则可以捕获到 panic 的输入值,并且恢复正常的执行
package main
import (
"fmt"
)
func div(a, b int) int {
defer fmt.Println("aaa")
ret := a / b // 发生除 0 异常时直接跳到 defer 函数
return ret
}
func done(a, b int) int {
defer func() { //必须声明为 defer 才能使用 recover 捕获 panic
fmt.Println("---")
if err := recover(); err != nil {
fmt.Println(err)
}
fmt.Println("===")
}()
defer fmt.Println("xxx")
cnt := div(a, b)
fmt.Printf("++++")
return cnt
}
func main() {
done(1, 0)
}
//aaa
//xxx
//---
//runtime error: integer divide by zero
//===
main 和 init 函数
go 里面有两个保留的函数: init 函数(能够应用于所有的package)和 main 函数(只能应用于 package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个 package 里面可以写任意多个 init 函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个 package 中每个文件只写一个 init 函数。
go 程序会自动调用 init() 和 main (),所以你不需要在任何地方调用这两个函数。每个 package 中的 init 函数都是可选的,但 package main 就必须包含一个 main 函数。
程序的初始化流程
从 main package 开始,先递归的导入所有的 package,然后从最后一层 package 开始, 同一个 package 下的多个文件按照命名字符串升序进行。同一个文件中按照全局 const 变量,全局普通变量,init 函数,main 函数的顺序进行初始化,若某个环节没有则跳到下一个环节。最后回溯到 main package 中的 main 函数
如下图: