Go語言學習查缺補漏ing Day7

恆生LIGHT雲社群發表於2021-12-10

作者:ReganYue

來源: 恆生LIGHT雲社群

Go語言學習查缺補漏ing Day7

零、前言

因為筆者基礎不牢,在使用Go語言的時候經常遇到很多摸不著頭腦的問題,所以筆者下定決心好好對Go語言進行查漏補缺,本【Go語言查缺補漏ing】系列主要是幫助新手Gopher更好的瞭解Go語言的易錯點、重難點。希望各位看官能夠喜歡,點點贊、關注一下唄!

一、再談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的順序問題。

這個程式執行結果是:

21

28
21

我們都知道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 []int

a := []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 five

0 zero
1 one
3 three
4 four

第二次執行結果如下:

0 zero

1 one
3 three
4 four
5 five

第三次執行結果如下:

4 four

5 five
0 zero
1 one
3 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()取隨機數決定的,所以每次執行,隨機數都不一樣,所以輸出順序也不一樣。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70001864/viewspace-2846899/,如需轉載,請註明出處,否則將追究法律責任。

相關文章