接口(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 main
import "fmt"
// 无参数
func sayHello() {
fmt.Println("Hello, world!")
}
// 相同类型的相邻参数,参数类型可合并
func add(x, y int) int {
return x + y
}
// 不定参数,为同一类型,用...表示;接收到的参数为切片类型
func sum(nums...int) int {
// 参数nums为切片类型
sum := 0
for _, 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做变参传入是,须使用“...”展开slice
myFunc("小明", 20)
}
值传递和引用传递
无论是值传递,还是引用传递,传递给函数的都是参数的副本。不过,值传递是值的copy,引用传递是地址的copy。
map、slice、chan、指针、interface默认以引用的方式传递。
package main
func 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] = 60
arr[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 main
import "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 + b
avg = (a + b) / 2
return
}
func main() {
//多返回值函数,必须使用多个变量接收,或“_”忽略。
//var s = make([]int, 2)
//s = calc(2, 4) //报错:assignment mismatch: 1 variable but calc returns 2 values
sum, _ := 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 return
return sum
}
return // 隐式返回 0
}
命名返回参数允许 defer 延迟调用通过闭包读取和修改。
func calc(a, b int) (sum int, avg int) {
defer func() {
sum += 100
}()
sum = a + b
avg = (a + b) / 2
return
}
func add(x, y int) int {
var sum int
defer func() {
sum += 100 // 修改无效
}()
sum = x + y
return sum
}
func main(){
sum, avg := calc(6, 10)
fmt.Println(sum, avg) // 116 8
fmt.Println(add(2, 8)) // 10
}
2. 匿名函数
匿名函数由一个不带函数名的函数声明和函数体构成。优势是可以直接使用函数内的变量,不必申明。
package main
import (
"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 field
s := struct{
fn func() string
}{
fn: func() string {
return "hello"
},
}
fmt.Println(s.fn())
// channel
cf := make(chan func() string, 2)
fc <- func() string { return "Say hello" }
fmt.Println((<-fc)())
}
3. 闭包、递归
3.1 闭包
参考资料
3.1.1 函数、引用环境
闭包是有函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)
**函数:**在闭包实际实现的时候,往往通过一个
外部函数
返回其内部函数
来实现。内部函数可能是内部实名函数
、匿名函数
或则一个lambda表达式
。引用环境:
- 在函数式语言中,当内嵌函数体内引用到函数体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。引用环境是指在程序执行中的某个点所有处于活跃状态的约束(一个变量的名称和其所代表的对象之间的联系)所组成的集合。
- 由于闭包把函数和运行时的引用环境打包成一个新的整体,所以就解决了函数编程中的嵌套所引发的问题。当每次调用包含闭包的函数是都将返回一个新的闭包实例,这些实例之间是隔离的,分别包含调用时不同的引用环境现场。
闭包与外部函数的生命周期
内函数对外函数的变量的修改,是对变量的引用。变量被引用后,它所在的函数结束,这个变量也不会马上被销毁;相当于变相延长了函数的生命周期。
package main
import "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 += 1
fmt.Println(*i)
}
}
func incr2(i int) func() {
return func() {
i += 1
fmt.Println(i)
}
}
func main() {
n := incrIn(100)()
fmt.Printf("%d\n\n", n)
i := 100
f1 := incr1(&i)
f2 := incr2(i)
f1() //101
f1() //102
f2() //101
f2() //102
fmt.Println()
i = 1000
f1() //1001
f1() //1002
f2() //103
f2() //104
fmt.Println()
incr1(&i)() // 1003
incr1(&i)() // 1004
incr2(i)() // 1005 每次调用都返回独立的闭包实例,这些实例是隔离的
incr2(i)() // 1005
fmt.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 := 1
fn := func() {
fmt.Printf("x = %d\n", x)
}
x = 100
return fn
}
func main(){
fns := delay1(100)
for _, fn := range fns {
fn()
}
// 输出:
// 100 + 3 = 103
// 100 + 3 = 103
// 100 + 3 = 103
delay2()() // 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: 3
gor2()
// 输出:
// gor2 v: 3
// gor2 v: 3
// gor2 v: 3
go gor3()
ch <- 1
ch <- 2
ch <- 3
ch <- 4
ch <- 11
time.Sleep(time.Duration(1) * time.Nanosecond)
ch <- 12
time.Sleep(time.Duration(1) * time.Nanosecond)
ch <- 13
time.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: 15
time.Sleep(5 * time.Second)
}
上面代码解析:
gor2()
内的匿名函数就是闭包(参考闭包内部函数的定义),v
为引用,且延长了v
的生命周期,在gor2()
中for-loop的遍历几乎是“瞬时”完成的,goroutine真正被执行在其后。所以输出都为 3。
gor3()
中,加入Sleep
机制,使得goroutine在赋值前执行。输出结果与赋值及goroutine执行时v
的实际值有关。
3.2 递归函数
递归,就是在运行的过程中调用自己。一个函数调用自己,就叫递归函数。
package main
import "fmt"
func factorial(i int) int {
if i <= 1 {
return 1
}
return i * factorial(i-1)
}
func main() {
var i int = 7
fmt.Printf("Factorial of %d is %d\n", i, factorial(i))
}
4. 延迟调用(defer)
4.1 defer特性及用途
defer 特性
- 关键字 defer 注册延迟调用。
- 延迟调用直到 return 前才被执行。因此可以用来做资源清理。
- 多个 defer 语句,按先进后出的方式执行。
- defer 语句中的变量,在 defer 声明时就已经决定了。
defer 用途
- 关闭文件句柄。
- 锁资源释放。
- 数据库连接释放。
package main
import (
"fmt"
"os"
)
func main() {
var whatever [3]struct{}
for i := range whatever {
defer fmt.Println(i)
}
//输出:
// 2
// 1
// 0
}
4.2 defer与闭包
defer中使用的匿名函数依然是一个闭包。
package main
import "fmt"
func main() {
var whatever [5]struct{}
for i := range whatever {
//函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4(i最新值),所以输出全都是4。
defer func() { fmt.Println(i) }()
}
x, y := 1, 2
defer func(a int) {
fmt.Printf("x:%d,y:%d\n", a, y) // defer匿名函数为闭包, y为引用。
}(x) // 复制 x 的值
x += 100
y += 100
fmt.Println(x, y)
fmt.Println()
// 输出:
// 101 202
// x:1,y:202
// 4
// 4
// 4
// 4
// 4
}
4.3 defer f.Close
package main
import "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 后面的语句在执行的时候,函数调用的参数会被保存起来,但不执行,也就是复制了一份。
// 方式1
defer t.Close()
// 方式2
tt := 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 main
import (
"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 / b
return
}
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 main
import (
"fmt"
)
func test() {
var run func() = nil
defer 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) int
func 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 operate
fmt.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) // 200
add := 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 main
import (
"fmt"
)
func demo1() {
defer func() {
// 验证是否有panic
if 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 channel
demo2() //报错: 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() // 无效 nil
defer fmt.Println("无效:", recover()) // 无效 nil
defer 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 int
func() {
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)) // 0
fmt.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)
})
}