首页 > 其他分享 >Go学习笔记

Go学习笔记

时间:2023-09-16 17:33:41浏览次数:53  
标签:defer adrx fmt 笔记 学习 re num func Go

这几天在学Go,记录一下一些困惑和理解

channel特性

查阅的资料:

Go语言channel探究_go 多个协程读一个channel_JE_Xie的博客-CSDN博客

Go 中的 channel 解析— Go 中的并发性 - 知乎 (zhihu.com)

Go中的channel_go channel_始梦的少年的博客-CSDN博客

一、channel与blocking和deadlock

之前我看到说,对一个channel进行空读和满写,就会发生deadlock,程序会报错,而这种说法事实上并不准确

当一个goroutine试图对channel进行空读或满写时,这个goroutine就会blocking,此时并不会报错,也就是说blocking对于goroutine来说是一个正常现象,blocking是可以被解除的

而只有当所有的goroutine都陷入blocking状态,并且没有别的goroutine可以解除他们的阻塞状态时,才回发生deadlock,因为此时程序已经无法正常进行了

defer特性

查阅的资料:

https://www.jb51.net/article/283251.htm

https://blog.csdn.net/lp15929801907/article/details/130202962

defer的基本用法就不在赘述,主要看一下defer的一些特性

一、多个defer执行顺序

多个defer的执行顺序是先进后出,也就是说defer后的操作是被押入到了一个栈中

看一下示例

package main

import "fmt"

func main() {
	for i := 1; i <= 3; i++ {
		defer fmt.Println(i)
	}
}

运行结果如下

二、defer中的参数

虽然defer语句是在延迟执行的,但是传递到defer后的参数是在defer声明时的值,而不是defer执行时的值

可以理解为defer拷贝了当时的数据进去,看一下示例

package main

import "fmt"

func main() {
	x := 10
	defer fmt.Println(x)
	x = 20
}

但是其中很重要的是,不是说只要defer后有变量,那变量的值就是当时的值了

因为defer会固定传入其中的参数的值,但defer后的语句中出现的变量不一定就是传入了参数

而传入了参数的变量,不一定传入的就是值,下面举个例子

package main

import "fmt"

func main() {
	x := 10
	adrx := &x
	defer fmt.Println("defer1", adrx, x, *adrx)
	defer func() { fmt.Println("defer2", adrx, x, *adrx) }()
	defer func(x int, adrx *int) { fmt.Println("defer3", adrx, x, *adrx) }(x, adrx)
	x = 20
	fmt.Println(adrx, x, *adrx)
}

首先看defer1,它声明时的x就是10,和一般情形是一样的

再看defer2,它输出的是20,20,但是defer2声明时的x是10

这是因为defer2后面声明了一个无参的匿名函数,它本质上并没有传递参数进去函数,在执行defer2的时候,他只是调用了匿名函数,匿名函数里面执行了方法,此时才将参数传入,也就是说是在执行的时候才传入的参数

而defer3则是一个有参的匿名函数,但是*adrx的值却和x不一样

这是因为defer3传入的adrx是一个指针变量,也就是说传入的是一个地址而不是值,而当我们执行defer3时,这个地址存储的值已经改变了,但我们的确拷贝了当时的地址

下面这个例子可以更加直观的理解defer3

package main

import "fmt"

func main() {
	x := 10
	adrx := &x
	defer func(x int, adrx *int) { fmt.Println("defer3", adrx, x, *adrx) }(x, adrx)
	x = 20
	y := 30
	adrx = &y
	fmt.Println(adrx, x, *adrx)
}

可以看到,即使我们在defer后面改变了adrx的值,输出的结果仍然没有改变

三、defer与匿名返回值和命名返回值

对于一个函数的返回值,有匿名返回值和命名返回值的两种形式,而defer在其中也有不同的影响

先看具体表现,然后再来分析

package main

import "fmt"

func def1() (re int) {
	defer func() { re++ }()
	return
}
func def2() (re int) {
	num := 1
	defer func() { re++ }()
	return num
}
func def3() (re int) {
	num := 1
	defer func() { num++ }()
	return num
}
func def4() (re int) {
	num := 1
	defer func() { num++ }()
	return
}
func def5() int {
	num := 1
	defer func() { num++ }()
	return num
}
func main() {
	fmt.Println(def1(), def2(), def3(), def4(), def5())
}

在分析之前,首先要关注一下return,事实上,return并不是一个原子操作,它分为三步

1、设置返回值 2、执行defer操作 3、返回返回值

在此基础之上,我们来分析一下这几个函数的返回结果

def1很好理解,不再赘述

def2是一个命名返回值的函数,因此实际上的return过程是将num赋值给re,执行defer,返回re,故结果为2

def3将def2中的defer换成了num++,因为我们返回的实际上是re,所以num++没有影响,故返回值是1

def4相比def3,我们直接return,也就是直接return re,甚至没有将num赋值给re,故结果是0,defer也没影响

def5是一个匿名返回值的函数,但它内部其实还是有一个“re”,不过没有显示表现出来

也就是说他的过程其实是将num赋值给这个没有直接表示出来的re,再对num++,本质上和def3没有区别

标签:defer,adrx,fmt,笔记,学习,re,num,func,Go
From: https://www.cnblogs.com/NLDQY/p/17707007.html

相关文章