函数,闭包,defer

func 函数名(参数)(返回值){
    函数体
}

go的函数中并没有函数重载,切记,切记
并且都是值传递
但是由于map.slice,interface,channel这些东西会由于指针,浅拷贝,然后可影响实参

函数中可以返回局部变量的地址,Go编译器使用栈逃逸机制将这种局部变量的空间分配在堆上

几种返回值的形式

return可以有参数,也可以没有参数,返回值可以有名称,也可以没有名称

  1. return关键字中指定了参数时,返回值可以不用名称。如果return没有参数, 则返回值必须带名称
  2. 当返回值有名称或者有多个返回值时,需要用括号括起来
  3. 即使返回值命名了,return中也可以强制指定其他返回值的名称,也就是说return的优先级更高
  4. 命名的返回值是预先声明好的,在函数内部可以直接使用且不用声明
package main

import "fmt"

//return指定了参数时,返回值可以么有名字
func sum(a, b int) int {
	ret := a + b
	return ret
}

//return省略参数,返回值必须带 名称,并且该名称可以直接使用,如果再次声明的话会报重定义的错误
func sum2(a, b int) (ret int) {
	ret = a + b
	return
}

//即使返回值 命名了,return也可以强制返回其他返回值的名称
func sum3(a, b int) (ret int) {
	res := a + b
	return res
}
func main() {
	r1 := sum(1, 2)
	fmt.Printf("r1: %v\n", r1)

	r2 := sum2(1, 2)
	fmt.Printf("r2: %v\n", r2)

	r3 := sum3(1, 2)
	fmt.Printf("r3: %v\n", r3)
}

函数的可变参数和多返回值

可变参数的函数:
可变参数必须作为函数的最后一个参数,通过在参数名后加 … 来标识
不定参数在函数体内相当于切片,对切片的操作同样适合对不定参数的操作
切片可以作为参数传递给不定参数

并且下面的函数中 写成 x, y… int64编译是报错的
可变参数的遍历也可以直接用for range

package main

import "fmt"

//普通参数声明
func intSum(x, y int64) int64 {
	return x + y
}

/*
可变参数的函数:
可变参数必须作为函数的最后一个参数,通过在参数名后加 ... 来标识
不定参数在函数体内相当于切片,对切片的操作同样适合对不定参数的操作
并且下面的函数中 写成 x, y... int64编译是报错的
*/
func intSum2(x int64, y ...int64) int64 {
	var res int64
	var i int64
	for i = 0; i < x; i++ {
		res += y[i]
	}
	return res
}

//多个返回值的函数,需要在函数签名中 用括号把返回值括起来
func intSum3(x int, y ...int) (bool, int) {
	var bRes bool = false
	var res int = 0
	if x > len(y) {
		bRes = false
		res = 0
	} else {
		for i := 0; i < x; i++ {
			res += y[i]
		}
		bRes = true
	}
	return bRes, res
}

func main() {
	var fun_1 bool = false
	if fun_1 {
		fmt.Println(intSum(1, 2))
	}

	var fun_2 bool = false
	if fun_2 {
		fmt.Println(intSum2(3, 1, 2, 3, 4, 5)) 
	}

	var fun_3 bool = true
	if fun_3 {
		fmt.Println(intSum3(5, 1, 2, 3))
		fmt.Println(intSum3(3, 1, 2, 3, 4))
	}

}

函数类型(感觉类似函数指针)

函数作为参数,返回值

package main

import (
	"fmt"
)

//函数作为类型(感觉像是 函数指针)
type calculation func(int, int) int

func add(x, y int) int {
	return x + y
}

func sub(x, y int) int {
	return x - y
}

//函数作为参数,由于做参数需要由参数名,所以需要定义成一种函数类型
func calc(x, y int, opti func(int, int) int) int {
	return opti(x, y)
}

//函数作为返回值,由于作为返回值不需要函数名,所以按照函数声明的格式写就好
func funRet() func(int, int) int {
	return sub
}

func main() {
	var bt1 bool = false
	if bt1 {
		var t1 calculation = add
		var t2 calculation = sub
		fmt.Println(t1(1, 2))
		fmt.Println(t2(1, 3))
	}

	var bt2 bool = false
	if bt2 {
		fmt.Println(calc(1, 2, add))
	}

	var bt3 bool = true
	if bt3 {
		fmt.Println(funRet()(1, 2))
	}
}

匿名函数

匿名函数多用于实现回调函数和闭包。
由于是匿名函数,所以没有函数名,只能通过某个变量保存然后作为立即执行的函数

package main

import "fmt"

//匿名函数被直接复制给函数变量 sum
var sum = func(a, b int) int {
	return a + b
}

func doinput(f func(int, int) int, a, b int) int {
	return f(a, b)
}

//匿名函数作为返回值
func warp() func(int, int) int {
	return func(a, b int) int {
		return a + b
	}
}

func main() {
	//匿名函数直接调用
	func(x, y int) {
		fmt.Println(x + y)
	}(1, 2)

	//匿名函数做实参
	doinput(func(a, b int) int {
		return a + b
	}, 1, 2)

	opFunc := warp()
	fmt.Printf("opFunc(1, 2): %v\n", opFunc(1, 2))
}

闭包

闭包首先是一个函数,功能类似于适配器
该函数包含了外部作用域的传入参数,并且在其返回的匿名函数中
可以使用外部传入的参数,这就相当于改变了函数的参数列表

即 闭包=函数+外部变量的引用,一般通过在匿名函数中引用外部函数的局部变量或包全局变量构成

底层原理:

  1. 函数可以作为返回值
  2. 函数内部查找变量时先在自己内部找,再往外层找
package main

import "fmt"

func adder(x, y int) func(int) int {
	return func(i int) int {
		return x + i + y
	}
}

func main() {
	ret := adder(100, 100)
	fmt.Println(ret(100))
} 

通过闭包对函数进行包装
把原来需要参数的函数f2,当做不需要参数的函数ret传给了f1

package main

import "fmt"

//对函数进行包装
/*
this is f1
this is f2
200
200
*/

func f1(f func() int) {
	fmt.Println("this is f1")
	fmt.Println(f())
}

func f2(x, y int) int {
	fmt.Println("this is f2")
	fmt.Println(x + y)
	return x + y
}

func test(fu func(int, int) int, x, y int) func() int {
	ret := func() int {
		return fu(x, y)
	}
	return ret
}

func main() {
	ret := test(f2, 100, 100)
	f1(ret)
	/*
		刚进入f1函数时,输出了this is f1
		然后开始调用f函数,也就是调用了ret函数,而ret函数来自test闭包
		ret实际就是 f2,则又输出了 thsi is f2和 200
		最后f函数输出了200
	*/
}

闭包的思考

闭包引用变量的生命周期

package main

import "fmt"

func adder() func(int) int {
	var x int
	ret := func(y int) int {
		x += y
		return x
	}
	return ret
}

/*变量f1是一个函数,并且引用了其外部作用域中的x变量,
则此时f就是一个闭包
在f的生命周期内,变量x将一直有效
可以理解为,每调用一次adder函数,将会产生一个栈空间
不论后续f1调用几次,都将共用同一个栈空间内的x变量
*/
func main() {
	f1 := adder()
	fmt.Println(f1(10))
	fmt.Println(f1(20))
	fmt.Println(f1(30))

	f2 := adder()
	fmt.Println(f2(10))
	fmt.Println(f2(10))
	fmt.Println(f2(10))
}
package main

import "fmt"

func calc(base int) (func(int) int, func(int) int) {
	add := func(i int) int {
		base += i
		return base
	}

	sub := func(i int) int {
		base -= i
		return base
	}
	return add, sub
}

func main() {
	f1, f2 := calc(10) //f1,f2共用一个base
	fmt.Println(f1(1), f2(2)) //11 9
	fmt.Println(f1(3), f2(4)) //12 8
	fmt.Println(f1(5), f2(6)) //13 7
}

defer

defer语句会将后面跟随的语句进行延迟处理。在defer所属的函数即将返回时
将延迟处理的语句按defer定义顺序逆序执行,最后defer的语句 最先执行
多用于资源清理,并且defer后面必须是函数或者方法调用
备注:主动调用方法退出进程时,defer将不会执行

package main

import "fmt"

func main() {
	fmt.Println("start")
	defer fmt.Println("1")
	defer fmt.Println("2")
	defer fmt.Println("3")
	fmt.Println("end")
    /*
    start
    end 
    3
    2
    1
    */
}

执行时间

在Go语言中return语句在底层执行时,共分为两步 以return x为例:

  1. 返回值赋值,将x的值赋给返回值(即将x的值存入寄存器中)
  2. RET指令:将寄存器中的值进行返回

而defer语句的执行时间在步骤1和步骤2之间

defer分析

变量作用域角度

package main

import "fmt"

/*
1:返回值赋值,由于返回值没有名称,所以开辟了一块内存存储5
2:defer修改x为6,但是由于修改的是x,所以不影响存储好的返回值
3:RET命令,返回之前存储好的 5
*/
func f1() int {
	x := 5
	defer func() {
		x++
	}()
	return x
}

/*
1:返回值赋值,x = 5
2:defer修改x为6,在寻找x时,找到了在返回值处声明的变量,故对其进行修改
3:RET命令,返回6
*/
func f2() (x int) {
	defer func() {
		x++
	}()
	return 5
}

/*
1:返回值赋值,y = x = 5
2:defer修改x为6,但是由于修改的是x,所以不影响存储好的返回值 y
3:RET命令,返回之前存储好的 y也就是5
*/
func f3() (y int) {
	x := 5
	defer func() {
		x++
	}()
	return x
}

/*
1:返回值赋值,x = 5
2:defer语句执行,在执行x++时,找到的x是传入的副本,不会对外面的变量产生影响(如果是传址,就会有影响)
3:RET命令,返回之前存储好的 5
*/
func f4() (x int) {
	defer func(x int) {
		x++
	}(x)
	return 5
}

func main() {
	fmt.Println(f1()) //5
	fmt.Println(f2()) //6
	fmt.Println(f3()) //5
	fmt.Println(f4()) //5
}

defer分析2

package main

import "fmt"

func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

//defer注册要延迟执行的函数时,该函数所有的参数都需要确定其数值,所以被嵌套的calc会先执行
/*
1:defer calc("AA", x, calc("A", x, y)) 执行到这里后延时处理
2:calc("A", x, y) A 1 2 3
3:def calc("AA", 1, 3)
4:x = 10
5:defer calc("BB", x, calc("B", x, y))
6:calc("B", x, y) B 10 2 12
7:defer calc("BB", x, 12)
8:y = 20
9:此时开始逆序执行 defer语句
10:首先执行第七行, BB 10 12 22
11:然后执行第三行,AA 1 3 4

A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4
*/
func main() {
	x := 1
	y := 2
	defer calc("AA", x, calc("A", x, y))
	x = 10
	defer calc("BB", x, calc("B", x, y))
	y = 20
}

init函数

快捷键为finit

初始化顺序:变量初始化->init()->main()

  1. init函数先于main函数自动执行,不能被调用,实现包级别的一些初始化操作
  2. 没有参数和返回值
  3. 每个包可以有多个init函数
  4. 包的每个源文件可以有多个init函数
  5. 同一个包的init执行顺序不确定
  6. 不同包的init函数按照包导入的依赖关系决定执行顺序
package main

import "fmt"

var a = initVar()

func initVar() int {
	fmt.Println("initVar...")
	return 100
}

func init() {
	fmt.Println("init...")
}

func main() {
	fmt.Println("main...")
}
initVar...
init...
main...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巴塞罗那的风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值