接口(interface)
- 函数
- 1. 函数定义
- 1.1. 函数名
- 1.2. 参数列表
- 1.3. 返回值列表
- 2. 匿名函数
- 3. 闭包、递归
- 3.1 闭包
- 3.1.1 函数、引用环境
- 3.1.2 闭包的延迟绑定
- 3.1.3 goroutine 的延迟绑定
- 3.2 递归函数
- 4. 延迟调用(defer)
- 4.1 defer特性及用途
- 4.2 defer与闭包
- 4.3 defer f.Close
- 4.4. defer陷阱
- 4.4.1. defer 与 closure
- 4.4.1. defer nil 函数
- 5. 高阶函数
- 6. 异常处理
上一篇:接口(interface)
下一篇:流程控制
函数
Go语言函数的特点:
1. 无需声明原型
2. 支持不定变参
3. 支持多返回值
4. 支持命名返回参数
5. 函数也是一种类型,一个函数可以复制给变量;可以作为参数传递给其他函数
6. 不支持嵌套(一个包不能有重名的函数)
7. 不支持重载
8. 不支持默认参数
1. 函数定义
使用关键字 func 定义函数,左大括号不能另起一行。
func 函数名(参数列表)(返回值列表) { // 左侧大括号不能另起一行函数体
}
1.1. 函数名
函数名是函数的标识符,在Go语言中,函数名必须遵循标识符的命名规则。
1.2. 参数列表
参数列表在函数定义时指出。函数定义时的参数,称为函数的形参
;当调用函数时,传递过来的变量就是函数的实参
。
- 函数可以没有参数,也可以有多个参数,参数之间用逗号隔开。
- 参数列表中,类型在变量名之后。
- 类型相同的相邻参数,参数类型可合并。
- 不定参数传值 就是函数的参数不是固定的,后面的类型是固定的; 可使用interface{}定义任意类型的不定参数。
- 不定参数必须是参数列表中最后一个参数。
package mainimport "fmt"// 无参数
func sayHello() {fmt.Println("Hello, world!")
}// 相同类型的相邻参数,参数类型可合并
func add(x, y int) int {return x + y
}// 不定参数,为同一类型,用...表示;接收到的参数为切片类型
func sum(nums...int) int {// 参数nums为切片类型sum := 0for _, num := range nums {sum += num}return sum
}// 使用interface{}定义任意类型的不定参数,使用类型断言接收参数值。
func myFunc(args ...interface{}) {name, _ := args[0].(string)age, _ := args[1].(int)fmt.Printf("%s今年%d岁!", name, age)
}func main() {sayHello()fmt.Println(add(1, 2))fmt.Println(sum(1, 2, 3, 4, 5))fmt.Println(sum([]int{1, 2, 3}...)) // 使用slice做变参传入是,须使用“...”展开slicemyFunc("小明", 20)
}
值传递和引用传递
无论是值传递,还是引用传递,传递给函数的都是参数的副本。不过,值传递是值的copy,引用传递是地址的copy。
map、slice、chan、指针、interface默认以引用的方式传递。
package mainfunc modifyArray(arr [3]int) [3]int {for i := range arr {arr[i] = arr[i] * 10}return arr
}// 1.如果是对数组某个完整元素值的进行修改,那么原有实参数组不变;
// 2.如果是对数组某个元素(切片)内的某个元素的值进行修改,那么原有数据也会跟着改变;
// 传参可以理解为浅copy,参数本身的指针是不同的,但元素指针相同,对元素指针所指向的内容操作会影响到传参过程中的原始数据。
func modifyMultiArray(arr [3][]int) [3][]int {for i := range arr[0] {arr[0][i] = arr[0][i] * 10 // 实际修改的为arr[0]引用类型值的指针所指向的内存的值,原始实参元素指向同样内存,因此也改变原始实参数据。}arr[1][2] = 60arr[2] = []int{7, 8, 9} // 修改整个引用类型元素的值,实际是给arr[2]重新赋值了一个指向新slice的指针值,原始实参元素指向的内存并未改变,因此不影响原始实参数据。return arr
}func main() {arr := [3]int{1, 2, 3}arrRes := modifyArray(arr)fmt.Println(arr) // [1 2 3],值传递函数内部修改并未改变arr变量fmt.Println(arrRes) // [10 20 30]arrSlice := [3][]int{{1, 2, 3},{4, 5, 6},}arrSlice1 := modifyMultiArray(arrSlice)fmt.Println(arrSlice) // [[10 20 30] [4 5 60] []]fmt.Println(arrSlice1) // [[10 20 30] [4 5 60] [7 8 9]]
}
1.3. 返回值列表
- 函数可以返回任意数量的返回值,返回值之间用逗号隔开,多返回值必须用括号。
- 可以使用 “_” 标识符忽略函数的某个返回值。
- Go语言返回值可以被命名,但不可与形参重名,且必须在函数体中使用;命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回。
- 有返回值的函数,必须有明确的终止(return)语句,否则会引发编译错误。
- Go语言不能使用容器接收多返回值,必须使用多个变量,或者“_”忽略。
package mainimport "fmt"func numbers() (int, int){ // 多返回值return 6, 8
}func add(x, y int) int { // 单个返回值,可省略括号return x + y
}func calc(a, b int) (sum int, avg int) { // 命名返回值必须使用括号,且不可与形参重名 sum = a + bavg = (a + b) / 2return
}func main() {//多返回值函数,必须使用多个变量接收,或“_”忽略。//var s = make([]int, 2)//s = calc(2, 4) //报错:assignment mismatch: 1 variable but calc returns 2 valuessum, _ := calc(2, 4)fmt.Println(sum) //6// 返回值作为其他函数调用实参fmt.Println(add(numbers()))
}
命名返回参数可被同名局部变量遮蔽,此时需要显式返回。
func add(x, y int) (sum int) {//var sum = 0 //同级代码块内变量不可重声明 Error:sum redeclared in this block{ //子代码块var sum = x + y// return //不可使用隐式返回 Error:result parameter sum not in scope at returnreturn sum}return // 隐式返回 0
}
命名返回参数允许 defer 延迟调用通过闭包读取和修改。
func calc(a, b int) (sum int, avg int) {defer func() {sum += 100}()sum = a + bavg = (a + b) / 2return
}func add(x, y int) int {var sum intdefer func() {sum += 100 // 修改无效}()sum = x + yreturn sum
}func main(){sum, avg := calc(6, 10)fmt.Println(sum, avg) // 116 8fmt.Println(add(2, 8)) // 10
}
2. 匿名函数
匿名函数由一个不带函数名的函数声明和函数体构成。优势是可以直接使用函数内的变量,不必申明。
package mainimport ("fmt""math"
)func main() {// Golang可以赋值给变量,做为结构字段,或在channel中传送// 变量getSqrt := func(a float64) float64 {return math.Sqrt(a)}fmt.Println(getSqrt(4))// collection cfn := []()func() string{func() string { return "hello" },func() string { return "world" },}// as fields := struct{fn func() string}{fn: func() string {return "hello"},} fmt.Println(s.fn())// channelcf := make(chan func() string, 2)fc <- func() string { return "Say hello" }fmt.Println((<-fc)())
}
3. 闭包、递归
3.1 闭包
参考资料
3.1.1 函数、引用环境
闭包是有函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)
**函数:**在闭包实际实现的时候,往往通过一个
外部函数
返回其内部函数
来实现。内部函数可能是内部实名函数
、匿名函数
或则一个lambda表达式
。引用环境:
- 在函数式语言中,当内嵌函数体内引用到函数体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。引用环境是指在程序执行中的某个点所有处于活跃状态的约束(一个变量的名称和其所代表的对象之间的联系)所组成的集合。
- 由于闭包把函数和运行时的引用环境打包成一个新的整体,所以就解决了函数编程中的嵌套所引发的问题。当每次调用包含闭包的函数是都将返回一个新的闭包实例,这些实例之间是隔离的,分别包含调用时不同的引用环境现场。
闭包与外部函数的生命周期
内函数对外函数的变量的修改,是对变量的引用。变量被引用后,它所在的函数结束,这个变量也不会马上被销毁;相当于变相延长了函数的生命周期。
package mainimport "fmt"func incrIn(n int) func() int {fmt.Printf("%p, %d\n", &n, n)return func() int {n++ // 内函数对外函数的变量的修改,是对变量的引用fmt.Printf("%p, %d\n", &n, n)return n}
}func incr1(i *int) func() {// 自由变量为引用传递,闭包则不再封闭,修改全局可见的变量,也会对闭包内的这个变量造成影响。return func() {*i += 1fmt.Println(*i)}
}func incr2(i int) func() {return func() {i += 1fmt.Println(i)}
}func main() {n := incrIn(100)()fmt.Printf("%d\n\n", n)i := 100f1 := incr1(&i)f2 := incr2(i)f1() //101 f1() //102f2() //101f2() //102fmt.Println()i = 1000f1() //1001 f1() //1002f2() //103f2() //104fmt.Println()incr1(&i)() // 1003incr1(&i)() // 1004incr2(i)() // 1005 每次调用都返回独立的闭包实例,这些实例是隔离的incr2(i)() // 1005fmt.Println()
}
3.1.2 闭包的延迟绑定
func delay1(x int) []func() {var fns []func()data := []int{1, 2, 3}for _, val := range data {fns = append(fns, func() {fmt.Printf("%d + %d = %d\n", x, val, x+val)})}return fns
}func delay2() func() {x := 1fn := func() {fmt.Printf("x = %d\n", x)}x = 100return fn
}func main(){fns := delay1(100)for _, fn := range fns {fn()}// 输出:// 100 + 3 = 103// 100 + 3 = 103// 100 + 3 = 103delay2()() // 100
}
上面代码解析:
闭包会保存相关引用的环境,也就是说变量在闭包的生命周期得到了保证;因此在执行闭包的时候,会去外部环境寻找最新的值。
delay1()
返回的仅是闭包函数的定义,只有在执行fn()
是在真正执行了闭包;执行时寻找最新的值3
。delay2
可以更直观的看到,实际执行的为x
最新值100
。
3.1.3 goroutine 的延迟绑定
func show(v interface{}) {fmt.Printf("show v: %v\n", v)
}func gor1() {data := []int{1, 2, 3}for _, v := range data {go show(v)}
}func gor2() {data := []int{1, 2, 3}for _, v := range data {go func() {fmt.Printf("gor2 v: %v\n", v)}()}
}var ch = make(chan int, 10)func gor3() {for v := range ch {go func() {fmt.Printf("gor 3 v: %v\n", v) // 闭包, v为引用}()}
}func main() {gor1() // goroutine执行顺序是随机的// 输出:// show v: 2// show v: 1// show v: 3gor2()// 输出:// gor2 v: 3// gor2 v: 3// gor2 v: 3go gor3()ch <- 1ch <- 2ch <- 3ch <- 4ch <- 11time.Sleep(time.Duration(1) * time.Nanosecond)ch <- 12time.Sleep(time.Duration(1) * time.Nanosecond)ch <- 13time.Sleep(time.Duration(1) * time.Nanosecond)ch <- 15// 输出:随机输出,大部分为11,个别为1~4// gor 3 v: 11// gor 3 v: 3// gor 3 v: 12// gor 3 v: 11// gor 3 v: 11// gor 3 v: 11// gor 3 v: 13// gor 3 v: 15time.Sleep(5 * time.Second)
}
上面代码解析:
gor2()
内的匿名函数就是闭包(参考闭包内部函数的定义),v
为引用,且延长了v
的生命周期,在gor2()
中for-loop的遍历几乎是“瞬时”完成的,goroutine真正被执行在其后。所以输出都为 3。
gor3()
中,加入Sleep
机制,使得goroutine在赋值前执行。输出结果与赋值及goroutine执行时v
的实际值有关。
3.2 递归函数
递归,就是在运行的过程中调用自己。一个函数调用自己,就叫递归函数。
package mainimport "fmt"func factorial(i int) int {if i <= 1 {return 1}return i * factorial(i-1)
}func main() {var i int = 7fmt.Printf("Factorial of %d is %d\n", i, factorial(i))
}
4. 延迟调用(defer)
4.1 defer特性及用途
defer 特性
- 关键字 defer 注册延迟调用。
- 延迟调用直到 return 前才被执行。因此可以用来做资源清理。
- 多个 defer 语句,按先进后出的方式执行。
- defer 语句中的变量,在 defer 声明时就已经决定了。
defer 用途
- 关闭文件句柄。
- 锁资源释放。
- 数据库连接释放。
package mainimport ("fmt""os"
)func main() {var whatever [3]struct{}for i := range whatever {defer fmt.Println(i)}//输出:// 2// 1// 0
}
4.2 defer与闭包
defer中使用的匿名函数依然是一个闭包。
package mainimport "fmt"func main() {var whatever [5]struct{}for i := range whatever {//函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4(i最新值),所以输出全都是4。defer func() { fmt.Println(i) }()}x, y := 1, 2defer func(a int) {fmt.Printf("x:%d,y:%d\n", a, y) // defer匿名函数为闭包, y为引用。}(x) // 复制 x 的值x += 100y += 100fmt.Println(x, y)fmt.Println()// 输出:// 101 202// x:1,y:202// 4// 4// 4// 4// 4
}
4.3 defer f.Close
package mainimport "fmt"type Test struct {name string
}func (T *Test) Close() {fmt.Println(T.name, "closed")
}func Closure() {//delay5()ts := []Test{{"a"}, {"b"}, {"c"}}for _, t := range ts {// defer 后面的语句在执行的时候,函数调用的参数会被保存起来,但不执行,也就是复制了一份。// 方式1defer t.Close()// 方式2tt := t // tt为内函数变量,不受外部函数变量改变影响。defer tt.Close()}// 方式1 输出:// c closed// c closed// c closed// 方式2 输出:// c closed// b closed// a closed
}
4.4. defer陷阱
4.4.1. defer 与 closure
package mainimport ("errors""fmt"
)func foo(a, b int) (i int, err error) {// 如果 defer 后面跟的不是一个闭包(closure),最后执行的时候我们得到的并不是最新的值。defer fmt.Printf("first defer err:%v\n", err) // 非闭包defer func(err error) {fmt.Printf("second defer err:%v\n", err) // 非闭包 }(err)defer func() {fmt.Printf("third defer err:%v\n", err) // 闭包}()if b == 0 {err = errors.New("divided by zero")return}i = a / breturn
}func main() {foo(8, 0)// 输出// third defer err:divided by zero// second defer err:<nil>// first defer err:<nil>
}
4.4.1. defer nil 函数
package mainimport ("fmt"
)func test() {var run func() = nildefer run()fmt.Println("runs")
}func main() {defer func() {if err := recover(); err != nil {fmt.Println(err)}}()test()// 输出// runs// runtime error: invalid memory address or nil pointer dereference
}
test
函数运行结束,然后defer
函数会被执行,且因为值为nil
而产生panic
异常。要注意的是,run()
的声明是没有问题,因为在test
函数执行完成后它才会被调用。
5. 高阶函数
高阶函数满足下面两个条件:
- 接受其他的函数作为参数传入
- 把其他的函数作为结果返回
函数类型
函数类型属于引用类型,它的值可以为nil,零值为nil。
package main// 函数类型声明
type operate func (x, y int) intfunc Calc(x, y int, op operate) (int, error) {if op == nil {return 0, errors.New("invalid operation")}return op(x, y), nil
}func getCalc(op operate) func(x, y int) (int, error) {return func(x, y int) (int, error) {if op == nil {return 0, errors.New("invalid operation")}return op(x, y), nil}
}func main() {var op operatefmt.Printf("%T, %#v", op, op) // main.operate, (main.operate)(nil)op = func(x, y int) int { return x + y }n, _ := Calc(100, 100, op)fmt.Println(n) // 200add := getCalc(op)v, err := add(10, 10)if err != nil {fmt.Println(err)} else {fmt.Println(v) // 20}
}
6. 异常处理
Go语言没有结构化异常,使用panic
函数来抛出异常,recover
捕获异常。
panic、recover 参数类型为interface{},可以处理任意类型。
panic
、recover
参数类型为interface{}
,可以处理任意类型。- 利用 recover 处理 panic 指令,defer 必须放在 panic 之前定义。
- recover 只有在 defer 调用的函数中才有效,否则当 panic 时,recover 无法捕获到 panic,无法防止 panic 扩散。
- recover 处理异常后,逻辑并不会回复到 panic 那个点去,函数跑到 defer 之后的那个点。
- 多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。
package mainimport ("fmt"
)func demo1() {defer func() {// 验证是否有panicif err := recover(); err != nil {fmt.Println("报错:", err)}}()ch := make(chan int, 10)close(ch)ch <- 1 // 向已关闭的通道发送数据,引发panic
}func demo2() {defer func() {fmt.Println("报错:", recover())}()defer func() {// defer 中引发的错误,可以被后续延迟调用捕获,但仅最后一个错误可被捕获。panic("defer panic")}()panic("code panic")
}func main() {demo1() //报错: send on closed channeldemo2() //报错: defer panic
}
recover函数只有在defer内直接调用才会终止错误,否则返回nil。任何未捕获的错误都会沿用堆栈向外传递
func except() {fmt.Println("函数输出错误:", recover())
}// recover 只有在 defer 调用的函数中才有效。
func main() {defer except() // 有效defer func() { // 有效fmt.Println("报错:", recover())panic("again panic")}()defer recover() // 无效 nildefer fmt.Println("无效:", recover()) // 无效 nildefer func() {func() {fmt.Println("defer:", recover()) // 无效 nil}()}()panic("panic error!")// 输出// defer: <nil>// 报错: panic error!// 函数输出错误: again panic// 无效: <nil>
}
常用的异常处理方式
- 保护代码段,将代码块重构成匿名函数。
func div1(x, y int) int {var z intfunc() {defer func() {if recover() != nil {z = 0}}()if y == 0 {panic("division by zero")}z = x / y}()return z
}
func main() {fmt.Println(div1(100, 10)) // 0fmt.Println(div1(100, 0)) // 10 fmt.Println()
}
- 除用 panic 引发中断性错误外,还可返回
error
类型错误对象来表示函数调用状态.
标准库errors.New
和fmt.Errorf
函数用于创建实现 error 接口的错误对象,通过判断错误对象实例来确定具体错误类型。
如何区别使用 panic
和 error
:导致关键流程出现不可修复性错误使用 panic
,其它使用 error
。
var errDivZero = errors.New("division by zero")func div(x, y int) (int, error) {if y == 0 {return 0, errDivZero}return x / y, nil
}func main() {defer func() {fmt.Println("错误:", recover())}()switch z, err := div(100, 0); err {case nil:fmt.Println("结果:", z)case errDivZero:panic(err)}
}
- Go 实现类似 try catch
func Try(fn func(), handler func(interface{})) {defer func() {if err := recover(); err != nil {handler(err)}}()fn()
}func main() {Try(func() {panic("Try panic")}, func(err interface{}) {fmt.Println(err)})
}