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指令执行之前。