8-函数
如果一个动作需要重复执行,那就应该把这个动作封装到函数中。
在 Go 中,函数是一等公民
定义
Go 语言中函数的基本形式:
| func 函数名(参数列表)(返回值){
函数体
}
|
- 函数名:由字母、数字、下划线组成,但不能数字开头;同一个包内,函数名不能重名
- 参数:由参数变量和变量类型组成,多个参数使用
,
分隔
- 返回值:由返回变量和变量类型,也可只写变量类型,多个返回值必须用
()
包裹,并用,
分隔
- 函数体:实现指定功能的代码
Example
求两数之和
| func sum(a int, b int) int {
return a + b
}
|
无参数无返回值的函数
| func sayHello() {
fmt.Println("Hello")
}
|
调用
通过函数名(参数)
的方式调用,如果定义的函数没有要求参数,则调用时不需要给参数
Example
| func main() {
res := sum(1, 3)
fmt.Println(res)
}
|
调用有返回值的函数时可以不接受其返回值。
参数
类型简写
如果相邻变量类型相同,可以只写一个
eg:
| func fn(x, y int, p, q string) {
fmt.Println(x, y, p, q)
}
|
x 和 y 均为 int 型,x 后面就可省略;p 和 q 都是 string 型,p 后面就可以省略
可变参数
如果参数数量不固定,可以在类型前面加上...
,但是只能接收这种类型的参数,且此时要注意类型简写的方法
注意
和固定参数搭配时,可变参数要放在固定参数后面
Example
| func fn(x int, y ...int) {
fmt.Println(x) // 1
fmt.Println(y) // [2 3 5]
fmt.Printf("%T \n", y) // []int
}
fn(1, 2, 3, 5)
// 错误,只能传递int型:fn(1, 2, "B", "C")
// 错误,全都要int型: fn(1, "A", "B", "C")
|
可变参数实际上是通过切片实现的。
-
关于类型简写:
| // 错误
func fn(x, y ...int) {}
// 正确
func fn(x ...int) {}
func fn(x int, y ...int) {}
func fn(x, z int, y ...int) {}
|
-
关于可变参数位置
| // 错误
func fn(y ...int, x int) {}
func fn(x int, y ...int, s string) {}
// 正确
func fn(x int, y ...int) {}
|
返回值
多返回值
Go 中支持多返回值,如果有多个返回值时,必须用()
包裹起来
Example
| func calc(x, y int) (int, int) {
return x + y, x - y
}
|
调用之后可以接收返回值,也可以不接收,如果指向接收一个,另一个可以用匿名变量 _
去接
| // 两个都接收
sum, sub := calc(10, 5)
// 两个都不接收
calc(10, 5)
// 只接收一个
_, sub := calc(10, 5)
sum, _ := calc(10, 5)
|
返回值命名
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return
返回
| func calc(x, y int) (sum, sub int){
sum = x + y
sub = x - y
return
}
|
如果给返回值命名了,但是又 return 了别的变量,以别的变量为准
| func calc(x, y int) (sum int, diff int) {
sum = x + y
diff = x - y
mul := x * y
div := x / y
return mul, div
}
fmt.Println(calc(10, 5)) // 50 2
|
上面的 return mul, div
可以看成 mul, div = sum, diff; return sum, diff
函数进阶
变量作用域
函数类型
函数可以有类型,使用关键字type
可以定义一个函数类型
eg:
| type calculation func(int, int) int
|
上面的例子定义了一个 calculation
类型,它是一种函数类型,一种接收两个int参数返回一个int值的函数类型。
方式满足上面特点的函数都是 calculation 类型。
Example
| func add(x int, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
|
add() 和 sub() 都是 calculation 类型,都可以赋值给 calculation 类型变量
| var c calculation
c = add
res := c(10, 5)
fmt.Println(res) // 15
s := sub
fmt.Println(s(20, 55)) // -35
|
函数变量
在 Go 中,函数被看作是第一类值,这意味着函数像变量一样,有类型、有值,普通变量能做的事函数也能做。
例如函数类型的例子,即使不定义函数类型,也可以直接将函数赋值给变量
| func add(x, y int) int {
return x + y
}
func main() {
a := add
fmt.Println(a(10, 20)) // 30
}
|
- 调用
nil
的函数变量会导致 panic
- 函数变量的零值
nil
,所以函数变量可以跟 nil
比较,但函数变量之间不能比较。
| package main
import "fmt"
type processFunc func(int) bool // 声明一个函数类型
func isOdd(i int) bool { // 定义这种类型的函数
return i%2 != 0
}
func isEven(i int) bool { // 定义这种类型的函数
return i%2 == 0
}
func filter(slice []int, f processFunc) []int { // 接收这种类型的函数
var res []int
for _, v := range slice {
if f(v) {
res = append(res, v)
}
}
return res
}
func main() {
s := []int{1, 2, 3, 4, 5, 7}
fmt.Println("s = ", s) // s = [1 2 3 4 5 7]
odd := filter(s, isOdd) // 调用这种类型的函数
even := filter(s, isEven) // 调用这种类型的函数
fmt.Println("Odd slice", odd) // Odd slice [1 3 5 7]
fmt.Println("Even slice", even) // Even slice [2 4]
}
|
匿名函数
没有名字的函数就叫匿名函数
基本语法:
匿名函数没有函数名,所以需要变量来保存,或者作为立即执行函数
| func main() {
// 将匿名函数保存到变量
add := func (x, y int) int {
return x + y
}
add(10, 20) // 通过变量调用匿名函数
}
|
| func main() {
// 自执行函数:匿名函数定义完加()直接执行
func (x, y int) {
fmt.Println(x + y)
}(10, 20)
}
|
高阶函数
高阶函数分为 函数作参数
和 函数作返回值
函数作为参数
| // 定义一个函数,使用函数作为参数
// @param x int 操作数1
// @param y int 操作数2
// @param operate func(int, int) int 操作函数
// @return int 经过操作函数操作的结果
func calc (x int, y int, operate func(int, int) int) int {
return operate(x, y)
}
// 定义一个操作函数
// @param x 操作数1
// @param y 操作数2
// @return 两个操作数相加的结果
func add(x, y int) int {
return x + y
}
func main() {
// 调用 calc 函数,把 add 函数传进去
res := calc(10, 20, add)
fmt.Println(res) // 30
}
|
函数作为返回值
| func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
// 定义一个函数,根据传入的操作符返回相应的操作函数
// @param s string 操作符
// @return func(int, int) int, error 操作函数
func do(s string) (func(int, int) int, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
return nil, errors.New("非法操作符")
}
}
func main() {
fn, err := do("+")
if err == nil {
fmt.Println(fn(10, 20)) // 20
}
}
|
闭包
Info
a closure is a record storing a function together with an environment.
闭包 是由 函数 和与其相关的引用 环境 组合而成的实体 。
闭包能将局部变量带出其作用域
| func adder() func() int {
var x int
return func() int {
x++
return x
}
}
func main() {
i := adder()
fmt.Println(i()) // 1
fmt.Println(i()) // 2
fmt.Println(i()) // 3
}
|
相当于:
| func adder() func() int {
var x int
f := func() int {
x++
return x
}
return f
}
func main() {
i := adder()
i() // 1
i() // 2
i() // 3
}
|
等于说,在 adder()
中声明的变量x
,本该在adder()
调用完就被销毁,却被闭包带出了x
的作用域(adder函数内),使得局部变量x
没有被销毁。
即局部变量x
逃逸了,它的生命周期没有随着它的作用域结束而结束。
defer 语句
defer 就是延迟的意思,被 defer
关键字修饰的函数或方法会延迟执行。
| func main() {
fmt.Println("start")
defer fmt.Println(1)
fmt.Println("end")
}
// ---------------------
// Output:
start
end
1
|
程序先执行第一句打印了 start,往下遇到 defer
,于是把第二句先压入栈,然后继续执行第三句打印了 end。
再继续往下已经没有,函数准备结束;但是在结束之前需先出栈,也就执行了第二句,打印了 1。最后结束。
这就是 defer
语句。
Info
由于 defer
语句延迟调用的特性,所以 defer
语句能非常方便的处理资源释放的问题。比如:关闭文件、关闭数据库连接、资源清理、释放锁、记录时间等等。
eg :
| func fn() {
f, err := os.Open("./main.go") // 打开文件
defer f.Close() // 函数退出之前关闭文件
if err != nil {
fmr.Println(err)
return
}
}
|
多个 defer 的执行顺序
当有多个 defer
时,按照栈的方式执行。即先defer
的后执行。
eg:
| func fn() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
}
// ---------------------
// Output:
start
end
3
2
1
|
defer 执行时机
Go 中的 return
语句并不是原子操作,它分为 给返回值赋值 和 RET指令 两步。
而 defer
语句执行的时机在返回值赋值之后,RET指令执行之前。