函数声明
//可变参数
func ex1(i int, j ...int) {
for index, _ := range j {
j[index] *= i
}
}
j := []int{1, 2, 3, 4, 5, 6, 7}
// slice 解析调用
ex1(2, j...)
fmt.Println(j) //[2 4 6 8 10 12 14]
//预设返回值
func ex2(s string) (i int) {
i, _ = strconv.Atoi(s)
return
}
//多返回值
func ex3(s string) (int, error) {
i, err := strconv.Atoi(s)
return i, err
}
- 可变参数,多返回值,裸返回,这是 go 函数的基本特点
- 不支持重载,在大型工程中,重载函数只会让你调用到你意料之外的函数
- 递归栈很大,一般的语言函数递归所能提供的最大栈大概 2k,而 go 函数递归提供动态的栈大小,最大有 2g
error
go 函数中最大的特点就是 error,在 go 的内置函数中,大多数函数第二个会返回一个 error 值,代表了调用函数失败的错误信息
通常我们并不能保障我们的函数一定会被成功调用,就需要返回 error 来明确提示错误信息,通常他会被作为第二个参数返回,如果调用结果只有两种情况,通常返回一个 bool 表示调用成功或失败
go 在设计时,希望能尽可能的处理错误信息,或尽可能的给出严谨的错误,如果说 java 只做正确的情况,对于所有错误情况通过异常抛出,那么 go 则是希望除了正确情况外,尽可能去处理意料之内的错误情况
提供几个 error 处理的函数
- fmt.Errorf() //返回格式化后的错误信息
- log.Fatal() //打印错误信息,默认带上服务器时间做前缀,可以自定义前缀
- 很多错误常量有利于我们对错误的处理,比如 io.EOF 代表文件读取到尾部了,读取不到了
函数变量
在 go 中,函数是一等公民,它也有类型,也可以作为变量赋值,传递,特有的就是可以调用
var f0 func(n int) int
func f1(n int) int {
return n*n
}
func f2(n int) int {
return n+n
}
func f3(n,m int) int {
return m*n
}
f := f1
fmt.Println(reflect.TypeOf(f)) //func(int) int
f = f2
fmt.Println(f(10)) // 20
//f = f3 报错,因为类型不同
fmt.Println(reflect.TypeOf(f3)) //func(int, int) int
fmt.Println(reflect.TypeOf(f0) == reflect.TypeOf(f1)) //true
// fmt.Println(f2 == f1) 函数之间无法直接比较,所以不能作为 map 的 key,但是函数可以和 nil 比较
//闭包
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
f := squares()
fmt.Println(f()) // 1
fmt.Println(f()) // 4
fmt.Println(f()) // 9
- 命名函数只能在包级别声明,但是我们可以在任意地方声明函数变量或使用函数字面量(匿名函数)
- 函数的零值是 nil,如果调用 nil 函数变量,会宕机
defer
func def() string{
defer fmt.Print("1")
defer fmt.Print("2")
defer fmt.Print("3")
return "ok"
def() //321
}
正常情况下,defer 修饰的语句会在 return 执行之后,逆序执行(堆),如果发生宕机,会在执行完 defer 后关闭程序
- 使用 os.Exit() 立即退出,则 defer 不会被执行
- 使用 panic 则 defer 会被执行