golang 函数 闭包 方法

defer用于资源的释放,会在函数返回之前进行调用。如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。

defer的触发时机

  1. 包裹着defer语句的函数返回时
  2. 包裹着defer语句的函数执行到最后时
  3. 当前goroutine发生Panic时

//输出结果:return前执行defer
func f1() {
	defer fmt.Println("return前执行defer")
	return
}

//输出结果:函数执行
// 函数执行到最后
func f2() {
	defer fmt.Println("函数执行到最后")
	fmt.Println("函数执行")
}

//输出结果:panic前  第一个defer在Panic发生时执行,第二个defer在Panic之后声明,不能执行到
func f3() {
	defer fmt.Println("panic前")
	panic("panic中")
	defer fmt.Println("panic后")
}

defer,return,返回值的执行顺序

  1. 先给返回值赋值
  2. 执行defer语句
  3. 包裹函数return返回
func f1() int { //匿名返回值
	var r int = 6
	defer func() {
		r *= 7
	}()
	return r
}

func f2() (r int) { //有名返回值
	defer func() {
		r *= 7
	}()
	return 6
}

func f3() (r int) { //有名返回值
	defer func(r int) {
		r *= 7
	}(r) // 复制 r 的值,defer执行时使用的是当时拷贝过来的r.函数传参机制
	return 6
}

f1的执行结果是6, f2的执行结果是42,f3的执行结果是6

最后看example3。它改写后变成

func f1() (r int) {
	r = 6 //给返回值赋值
	func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值
		r *= 7
	}(r)
	return //空的return
}

f1的结果是6。f1是匿名返回值,匿名返回值是在return执行时被声明,因此defer声明时,还不能访问到匿名返回值,defer的修改不会影响到返回值。
f2先给返回值r赋值,r=6,执行defer语句,defer修改r, r = 42,然后函数return。
f3是有名返回值,但是因为r是作为defer的传参,在声明defer的时候,就进行参数拷贝传递,所以defer只会对defer函数的局部参数有影响,不会影响到调用函数的返回值。

var a bool = true

func main() { // 2 1
	defer func() {
		fmt.Println("1")
	}()
	if a == true {
		fmt.Println("2")
		return
	}
	defer func() {
		fmt.Println("3")
	}()
}

return 之后的 defer 是不能注册的, 也就不能执行后面的函数或方法 

闭包与匿名函数

匿名函数:没有函数名的函数。函数也是一种类型,一个函数可以赋值给变量
闭包:在函数中返回匿名函数,匿名函数可以使用函数作用域中的变量。

for i := 0; i <= 3; i++ {
    defer func() {
        fmt.Print(i)
    }
}
//输出结果时 3,3,3,3
因为defer函数的i是对for循环i的引用,defer延迟执行,for循环到最后i是3,到defer执行时i就是3

for i := 0; i <= 3; i++ {
    defer func(i int) {
        fmt.Print(i)
    }(i)
}
//输出结果时 3,2,1,0
因为defer函数的i是在defer声明的时候,就当作defer参数传递到defer函数中

匿名函数

func main() {
	/* 匿名函数切片初始化 */
	fns := [](func(x int) int){
		func(x int) int { return x + 1 },
		func(x int) int { return x + 113 },
	}
	println(fns[1](100))

	/* 结构体初始化 */
	d := struct {
		fn func() string
	}{
		fn: func() string { return "Hello, World!" },
	}
	println(d.fn())

	fc := make(chan func() string, 2)
	fc <- func() string { return "Hello, World!" }
	println((<-fc)())
}

闭包

package main

import "fmt"

func test() func() {
	x := 100
	fmt.Printf("x (%p) = %d\n", &x, x)

	return func() {
		fmt.Printf("x (%p) = %d\n", &x, x)
	}
}

func main() {
	f := test()
	f()
}

输出:

x (0xc42007c008) = 100
x (0xc42007c008) = 100

闭包复制的是原对象指针,这就很容易解释延迟引用现象。函数test返回了一个函数,返回的这个函数就是一个闭包。这个函数中本身是没有定义变量x的,而是引用了它所在的环境(函数test)中的变量x。

go语言中 函数式编程可用函数作为类型 将函数类型传递到函数里面直接可以使用

func fibonacco() intGen {
	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		return a
	}
}

type intGen func() int //将函数定义为类型
func (g intGen) Read(p []byte) (n int, err error) { //定义接口 go语言中任何类型都可以实现接口 函数只是一个特殊的参数
	next := g()       //next是 类型传输值
	if next > 10000 { //限制无限循环
		return 0, io.EOF
	}
	s := fmt.Sprintf("%d\n", next)      //将整形转化为字符串 strings.NewReader使用
	return strings.NewReader(s).Read(p) //使用s做打印 p为uint8可使用位数
}
func printFileContents(reader io.Reader) { //打印任何类型
	scanner := bufio.NewScanner(reader) //将函数转换 赋值给scanner

	for scanner.Scan() { //将scanner里面的值全部循环打印出来
		fmt.Println(scanner.Text())
	}
}

func main() {
	a := fibonacco()
	printFileContents(a)
}

类似的

func hello() []string {
	return nil
}

func main() {
	h := hello
	if h == nil {
		fmt.Println("nil")
	} else {
		fmt.Println("not nil")
	}

	/*myPrintln := fmt.Println
	myPrintln(myPrintln)
	myPrintln("内容")*/
}

是将 hello() 赋值给变量 h,而不是函数的返回值,所以输出 not nil

全局变量特点

  • 常驻内存
  • 污染全局

局部变量特点

  • 不常驻内存
  • 不污染全局

闭包特点

  • 可以让一个变量常驻内存
  • 可以让一个变量不污染全局

函数与方法的区别

1、对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。
2、对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以。

Go方法规则

根据调用者不同,方法分为两种表现形式:

instance.method(args...) ---> <type>.func(instance, args...)

前者称为 method value,后者 method expression。两者都可像普通函数那样赋值和传参,区别在于 method value 绑定实例,而 method expression 则须显式传参。

package main

import "fmt"

type User struct {
	id   int
	name string
}

func (self *User) Test() {
	fmt.Printf("%p, %v\n", self, self)
}

func main() {
	u := User{1, "Tom"}
	u.Test()

	mValue := u.Test
	mValue() // 隐式传递 receiver

	mExpression := (*User).Test
	mExpression(&u) // 显式传递 receiver
}

需要注意,method value 会复制 receiver

package main

import "fmt"

type User struct {
	id   int
	name string
}

func (self User) Test() {
	fmt.Println(self)
}

func main() {
	u := User{1, "Tom"}
	mValue := u.Test // 立即复制 receiver,因为不是指针类型,不受后续修改影响。

	u.id, u.name = 2, "Jack"
	u.Test()

	mValue()
}

输出结果

{2 Jack}
{1 Tom}

可依据方法集转换 method expression,注意 receiver 类型的差异。

package main

import "fmt"

type User struct {
	id   int
	name string
}

func (self *User) TestPointer() {
	fmt.Printf("TestPointer: %p, %v\n", self, self)
}

func (self User) TestValue() {
	fmt.Printf("TestValue: %p, %v\n", &self, self)
}

func main() {
	u := User{1, "Tom"}
	fmt.Printf("User: %p, %v\n", &u, u)

	mv := User.TestValue
	mv(u)

	mp := (*User).TestPointer
	mp(&u)

	mp2 := (*User).TestValue // *User 方法集包含 TestValue。签名变为 func TestValue(self *User)。实际依然是 receiver value copy。
	mp2(&u)
}

输出:

User: 0xc000004078, {1 Tom}
TestValue: 0xc0000040a8, {1 Tom}
TestPointer: 0xc000004078, &{1 Tom}
TestValue: 0xc0000040f0, {1 Tom}

将方法 "还原" 成函数,就容易理解下面的代码了。

package main

type Data struct{}

func (Data) TestValue() {}

func (*Data) TestPointer() {}

func main() {
	var p *Data = nil
	p.TestPointer()

	(*Data)(nil).TestPointer() // method value
	(*Data).TestPointer(nil)   // method expression

	// p.TestValue()            // invalid memory address or nil pointer dereference

	// (Data)(nil).TestValue()  // cannot convert nil to type Data
	// Data.TestValue(nil)      // cannot use nil as type Data in function argument
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值