写点什么

Go 语言学习查缺补漏 ing Day7

作者:Regan Yue
  • 2021 年 11 月 25 日
  • 本文字数:2101 字

    阅读完需:约 7 分钟

Go语言学习查缺补漏ing Day7

Go 语言学习查缺补漏 ing Day7

一、再谈 defer 的执行顺序

大家来看一看这段代码:


package main
import "fmt"
type Person struct { age int}func main() { person := &Person{28}
//A defer func(p *Person) { fmt.Println(p.age) }(person) //B defer fmt.Println(person.age) //C defer func() { fmt.Println(person.age) }() person.age = 21}
复制代码


前面我们介绍过 defer 的执行顺序,但是我今天又遇到新问题,于是这里又补充介绍这个 defer 的顺序问题。


这个程序运行结果是:


212821
复制代码


我们都知道 defer 的执行顺序是先进后出,所以执行顺序是 C、B、A。


B 中defer fmt.Println(person.age)输出 28,为什么呢?


因为这里是将 28 作为 defer()函数的参数,会把 28 推入栈中进行缓存,得到执行这条 defer 语句时就把它拿出来。所以输出 28.


而 A 中:


defer func(p *Person) {    fmt.Println(p.age)  }(person)
复制代码


defer()函数是将结构体 Person 的地址进行缓存,当后续改变这个地址的内值时,后续输出时这里就会输出那个地址内改变后的值。所以 B defer 语句执行时从地址中取出的值是 29.


而 C defer 语句理由很简单:


defer func() {    fmt.Println(person.age)  }()
复制代码


就是无参匿名函数的一种情形。闭包引用,person.age 改变就会改变。

二、哪种切片的声明比较好?为什么?

var a []inta := []int{}
复制代码


这里第一种声明的是 nil 切片,而第二种声明是创建一个长度以及容量为零的空切片。


第一种切片声明方法比较好,因为它这种声明方式不占用空间,而第二种声明后会占用一部分空间。

三、取得结构体成员的几种方法

package main
import "fmt"
type S struct { m string}
func f() *S { return &S{"ReganYue"}}func main() { p := f() p2 := *f() fmt.Println(p.m, p2.m)}
复制代码


我们运行能够发现:


p、p2 都能获取结构体的成员变量。


为什么呢?f()函数的返回值是指针类型,所以 p2 获取*f()时,p2 是 S 类型,p2.m 可以获取其成员变量。


而 f()的结果是指针,不过我们前面说过,一级指针能够自动进行解引用。所以也能够访问成员变量。

四、遍历 map 的存在顺序变化?为什么?

我们执行下面这段代码多次,看输出结果:


package main
import "fmt"
func main() { m := map[int]string{0: "zero", 1: "one", 3: "three", 4: "four", 5: "five"} for k, v := range m { fmt.Println(k, v) }}
复制代码


第一次执行结果如下:


5 five0 zero1 one3 three4 four
复制代码


第二次执行结果如下:


0 zero1 one3 three4 four5 five
复制代码


第三次执行结果如下:


4 four5 five0 zero1 one3 three
复制代码


我们发现每一次执行的顺序都是变化的。这说明遍历 map 的顺序是无序的。为什么呢?


在 runtime.mapiterinit 中有这样一段代码:


// mapiterinit initializes the hiter struct used for ranging over maps.// The hiter struct pointed to by 'it' is allocated on the stack// by the compilers order pass or on the heap by reflect_mapiterinit.// Both need to have zeroed hiter since the struct contains pointers.func mapiterinit(t *maptype, h *hmap, it *hiter) {  if raceenabled && h != nil {    callerpc := getcallerpc()    racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapiterinit))  }
if h == nil || h.count == 0 { return }
if unsafe.Sizeof(hiter{})/sys.PtrSize != 12 { throw("hash_iter size incorrect") // see cmd/compile/internal/gc/reflect.go } it.t = t it.h = h
// grab snapshot of bucket state it.B = h.B it.buckets = h.buckets if t.bucket.ptrdata == 0 { // Allocate the current slice and remember pointers to both current and old. // This preserves all relevant overflow buckets alive even if // the table grows and/or overflow buckets are added to the table // while we are iterating. h.createOverflow() it.overflow = h.extra.overflow it.oldoverflow = h.extra.oldoverflow }
// decide where to start r := uintptr(fastrand()) if h.B > 31-bucketCntBits { r += uintptr(fastrand()) << 31 } it.startBucket = r & bucketMask(h.B) it.offset = uint8(r >> h.B & (bucketCnt - 1))
// iterator state it.bucket = it.startBucket
// Remember we have an iterator. // Can run concurrently with another mapiterinit(). if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator { atomic.Or8(&h.flags, iterator|oldIterator) }
mapiternext(it)}
复制代码


  // decide where to start  r := uintptr(fastrand())  if h.B > 31-bucketCntBits {    r += uintptr(fastrand()) << 31  }  it.startBucket = r & bucketMask(h.B)  it.offset = uint8(r >> h.B & (bucketCnt - 1))
// iterator state it.bucket = it.startBucket
复制代码


我们可以看到,决定从哪开始是根据 fastrand()取随机数决定的,所以每次运行,随机数都不一样,所以输出顺序也不一样。

发布于: 2021 年 11 月 25 日阅读数: 9
用户头像

Regan Yue

关注

还未添加个人签名 2020.08.12 加入

对Go、Python、网络安全、区块链感兴趣. · 华为云云享专家 · 掘金资讯创作者

评论

发布
暂无评论
Go语言学习查缺补漏ing Day7