Golang之函数
一、函数定义
Go语言中支持函数、匿名函数和闭包,下面看一下函数的定义
// 第一个小括号当中是参数列表,
// 第二个小括号是返回值列表
func A(a int, b string) (int, string, int) {
}
// 如果abc都是int型的话,可以按照这种方法进行简写,同样的方法也适用于返回值当中
func A(a, b, c int) (int, string, int) {
}
// 无返回值
func A() {}
命名返回值
func a(x, y int) (a, b int) {
a = x + y
b = x - y
return
}
func TestOne(t *testing.T) {
t.Log(a(12, 4)) // 16, 8
}
二、可变参数
import "testing"
func Snm(ops ...int) int {
ret := 0
for _, op := range ops {
ret += op
}
return ret
}
func TestTimeSpent(t *testing.T) {
t.Log(Snm(1, 3, 5, 7, 9))
}
三、函数类型和变量
使用type来定义的函数类型
// 定义了一个calc类型,它是一种函数类型,
// 这种函数接收两个int类型的参数,返回一个int类型的返回值。
type calcu func(int, int) int
func add(a, b int) int {
return a + b
}
// 使用
func TestFuncOne(t *testing.T) {
var c calcu
c = add
t.Log(c(12, 45))
}
四、函数作为参数
type calcu func(int, int) int
func add(a, b int) int {
return a + b
}
// 函数最为参数,可以直接上面定义的函数类型
func sum(a, b int, c calcu) int {
return c(a, b)
}
// 也可以使用下面这种,这两个使用其实是一样的
func sum1(a, b int, c func(int, int) int) int {
return c(a, b)
}
func TestFuncTwo(t *testing.T) {
res1 := sum(1, 2, add)
res2 := sum1(1, 2, add)
t.Log(res1)
t.Log(res2)
}
五、函数作为返回值
type calcu func(int, int) int
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
func selectOption(s string) (calcu, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("未知函数")
return nil, err
}
}
func TestFuncThree(t *testing.T) {
option, err := selectOption("/")
if err != nil {
panic(err)
}
t.Log(option(1, 2))
}
六、匿名函数
所谓匿名函数,就是没有名字的函数
6.1. 匿名函数的使用方式:
func TestFuncFour(t *testing.T) {
// 在定义匿名函数的时候就可以直接使用(这种方式只使用一次)
func1 := func(a, b int) int {
return a + b
} (1, 2)
t.Log(func1)
// 将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
add := func(a, b int) int {
return a + b
}
func2 := add(1, 2)
t.Log(func2)
}
6.2 全局匿名函数
全局匿名函数就是将匿名函数赋给一个全局变量,那么这个匿名函数在当前程序里可以使用
var (
// Add 就是定义好的全局变量
// 全局变量必须首字母大写
Add = func(a, b int) int {
return a + b
}
)
func main() {
fmt.Println(add(1, 3))
}
七、函数闭包
闭包是匿名函数与匿名函数所引用环境的组合。匿名函数有动态创建的特性,该特性使得匿名函数不用通过参数传递的方式,就可以直接引用外部的变量。
7.1. 闭包作为函数返回值
func Increase() func () int {
n := 0
return func() int {
n++
return n
}
}
func TestFuncFive(t *testing.T) {
out := Increase()
t.Log(out())
t.Log(out())
t.Log(out())
}
7.2. 返回多个内部函数
func calc1(a int) (func(int) int, func(int) int) {
add := func(i int) int {
a += i
return a
}
sub := func(i int) int {
a -= i
return a
}
return add, sub
}
func TestSix(t *testing.T) {
f1, f2 := calc1(100)
t.Log(f1(1), f2(2)) // 101 99
t.Log(f1(1), f2(2)) // 100 98
t.Log(f1(2), f2(3)) // 100 97
}
其他的使用在golang的并发中在介绍吧
八、延迟函数defer
Go语言中的defer
语句会将其后面跟随的语句进行延迟处理。在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行,也就是说,先被defer
的语句最后被执行,最后被defer
的语句,最先被执行。
defer
通常用于简化函数的各种各样清理动作,例如关闭文件,解锁等等的释放资源的动作。
func TestSeven(t *testing.T) {
defer fmt.Println("No.0")
fmt.Println("start")
defer fmt.Println("No.1")
defer fmt.Println("No.2")
fmt.Println("end")
defer fmt.Println("No.3")
}
// 输入结果
start
end
No.3
No.2
No.1
No.0
如果返回值是无名的, func test() int {}
则go会在return 的时候创建一个临时的变量s
保存return值的动作,之后的defer
函数不能操作临时变量s
,只能操作变量i
。
func test() int {
var i int
defer func() {
i++
fmt.Println("defer1 = >", i)
}()
defer func() {
i++
fmt.Println("defer2 = >", i)
}()
return i
}
func TestFuncTen(t *testing.T) {
t.Log(test())
}
// 输出结果
defer2 = > 1
defer1 = > 2
func_test.go:162: 0
如果返回值是有名的(eg : func test() (i int ) {}
) 那么在执行return的时候,就不会创造临时变量去保存i
,之后的defer
函数可以操作i
。
func test1() (i int) {
defer func() {
i++
fmt.Println("defer1 = >", i)
}()
defer func() {
i++
fmt.Println("defer2 = >", i)
}()
return i
}
func TestFuncTen(t *testing.T) {
t.Log(test1())
}
// 输出结果
defer2 = > 1
defer1 = > 2
func_test.go:164: 2
在Go语言的函数中return
语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer
语句执行的时机就在返回值赋值操作后,RET指令执行前。
另外 defer
函数会先于 panic
之前执行。
func TestDefer(t *testing.T) {
defer func() {
t.Log("Clear resources")
}()
t.Log("Started")
panic("Fail error")
}
结果:
=== RUN TestDefer
func_test.go:22: Started
func_test.go:20: Clear resources
--- FAIL: TestDefer (0.00s)
panic: Fail error [recovered]
panic: Fail error
九、内置函数
名称 | 作用 |
---|---|
close | 用来关闭channel |
len | 用来求长度 |
cap | 用来返回某一个类型最大容量(只用于切片和map) |
new | 用来分配内存,主要用来分配值类型,例如int,struct,返回的是指针 |
make | 用来分配内存,主要用来分配应用类型,例如:chan, map, slice |
copy | 用来复制元素 |
append | 用来追加元素到数组,slice中 |
panic/recover | 用于错误处理机制 |
print/println | 底层打印函数,在项目中建议使用fmt包 |
complex/real imag | 用于操作和处理复数 |
十、 panic/recover
这里需要强调下:panic
可以在任何地方引发,但recover
只有在defer
调用的函数中有效。
10.1 panic
panic
是内建的停止控制流的函数。相当于其他编程语言的抛异常操作。当函数F调用了 panic
,F的执行会被停止,在F中 panic
前面定义的 defer
操作都会被执行,然后F函数返回。对于调用者来说,调用F的行为就像调用 panic
(如果F函数内部没有把 panic recover
掉)。如果都没有捕获该 panic
,相当于一层层 panic
,程序将会 crash
。 panic
可以直接调用,也可以是程序运行时错误导致,例如数组越界。
10.2 recover
recover
是一个从 panic
恢复的内建函数。 recover
只有在 defer
的函数里面才能发挥真正的作用。如果是正常的情况(没有发生 panic
),调用 recover
将会返回 nil
并且没有任何影响。如果当前的 goroutine panic
了, recover
的调用将会捕获到 panic
的值,并且恢复正常执行。
func funcA() {
fmt.Println("func A")
}
func funcB() {
defer func() {
err := recover()
//如果程序出出现了panic错误,可以通过recover恢复过来
if err != nil {
fmt.Println("recover in B")
}
}()
panic("panic in B")
}
func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funcC()
}
// 输出
func A
recover in B
func C
使用注意: recover()
必须搭配 defer
使用; defer
一定要在可能引发 panic
的语句前面定义。