掌握這些Go語言特性,你的水平將提升N個檔次(二)

前言: 你們好,我是asong,這是個人第二篇原創文章。上一文介紹了切片、變量聲明、defer三個知識點(回顧上文,關注公衆號便可進行閱讀),這一文將繼續介紹其餘Go語言特性,廢話很少說,直接上乾貨。面試

1. 指針和引用

在Go語言中只有一種參數傳遞的規則,那就是值拷貝,其包含兩種含義:sql

  • 函數參數傳遞時使用的值拷貝
  • 實例賦值給接口變量,接口對實例的引用是值拷貝

咱們在使用過程當中會發現有時明明是值拷貝的地方,結果卻修改了變量的內容,有如下兩種狀況:編程

  • 直接傳遞的是指針。指針傳遞一樣是值拷貝,但指針和指針副本的值指向的地址是同一個地方,因此能修改實參
  • 參數是複合數據類型,這些符合數據類型內部有指針類型的元素,此時參數的值拷貝並不影響指針的指向。

在Go語言中,複合類型chan、map、slice、interface內部都是經過指針指向具體的數據,這些類型的變量在做爲函數參數傳遞時,實際上至關於指針的副本。咱們能夠經過查看源碼,看一看他們的底層數據結構:數組

  1. map的底層數據結構:
//src/runtime/map.go1.14
// A header for a Go map.
type hmap struct {
   // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
   // Make sure this stays in sync with the compiler's definition.
   count     int // # live cells == size of map. Must be first (used by len() builtin)
   flags     uint8
   B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
   noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
   hash0     uint32 // hash seed
   buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
   oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
   nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
   extra *mapextra // optional fields
}
複製代碼

經過源碼咱們能夠分析,其經過buckets指針來間接引用map中的存儲結構。 2. slice的底層數據結構:數據結構

//src/reflect/value.go1.14
// sliceHeader is a safe version of SliceHeader used within this package.
type sliceHeader struct {
   Data unsafe.Pointer
   Len  int
   Cap  int
}
複製代碼

slice則採用uinptr指針指向底層存放數據的數組。 3. interface的底層數據結構以下:app

//src/reflect/value.go1.14
// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
   // see ../runtime/iface.go:/Itab
   itab *struct {
      ityp *rtype // static interface type
      typ  *rtype // dynamic concrete type
      hash uint32 // copy of typ.hash
      _    [4]byte
      fun  [100000]unsafe.Pointer // method table
   }
   word unsafe.Pointer
}
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}
複製代碼

咱們能夠看到接口內部經過一個指針指向實例值或地址的副本。 4. chan的底層數據結構以下:less

//src/runtime/chan.go1.14
type hchan struct {
   qcount   uint           // total data in the queue
   dataqsiz uint           // size of the circular queue
   buf      unsafe.Pointer // points to an array of dataqsiz elements
   elemsize uint16
   closed   uint32
   elemtype *_type // element type
   sendx    uint   // send index
   recvx    uint   // receive index
   recvq    waitq  // list of recv waiters
   sendq    waitq  // list of send waiters
   // lock protects all fields in hchan, as well as several
   // fields in sudogs blocked on this channel.
   //
   // Do not change another G's status while holding this lock
   // (in particular, do not ready a G), as this can deadlock
   // with stack shrinking.
   lock mutex
}
複製代碼

經過源碼咱們能夠看出,通道元素的存放地址由buf指針肯定,chan內部的數據也是間接經過指針訪問的。函數

2. 函數

Go語言支持匿名函數,其函數名和匿名函數字面量的值有3層含義:工具

  • 類型信息,代表其數據類型是函數類型學習

  • 函數名錶明函數的執行代碼的起始位置

  • 能夠經過函數名進行函數調用,函數調用格式爲 func_name(param_list)。在底層執行層面包含如下4部份內容。

  • 準備好參數

  • 修改PC值,跳轉到函數代碼起始位置開始執行

  • 複製值到函數的返回值棧區

  • 經過RET返回到函數調用的下一條指令處繼續執行。

2). 函數的方法設計 咱們在開發時,有時內部會實現兩個"同名"的函數或方法,一個首字母大寫,用於導出API供外部調用;一個首字母小寫,用於實現具體邏輯。通常首字母大寫的函數調用首字母小寫的函數,同時包裝一些功能;首字母小寫的函數負責更多的底層細節。 大部分狀況下咱們不須要兩個同名且只是首字母大小寫不一樣的函數,只有在函數邏輯很複雜,並且函數在包的內、外部都被調用的狀況下,才考慮拆分爲兩個函數進行實現。一方面減小單個函數的複雜性,另外一方面進行調用隔離。

這種編程方法在database/sql庫中體現較明顯,有興趣的能夠查看這一部分的源碼。 3) 多值返回函數設計 Go語言支持多值返回函數,這裏不對多值返回函數基礎使用進行介紹,這裏只介紹多值返回函數的推薦編程風格方法。 多值返回函數裏若是有error或bool類型的返回值,則應該將error或bool做爲最後一個返回值。這是一種編程風格,沒有對錯。Go標準庫的寫法也遵循這樣的規則。當大多數人都使用、遵循這種方法時,若是有人不遵循這種"潛規則",則寫出的代碼會讓別人讀起來就會很彆扭。因此推薦大家開發時這樣進行書寫。示例以下:

func testBool() (int ,bool){}
func testError() (int,error){}
複製代碼

3. 代碼風格

Go做爲新世紀開發的一門語言,其做者在代碼乾淨上有了近乎苛刻的要求,有以下幾方面的體現: 1) 編譯器不能經過未使用的局部變量。 2)"import"未使用的包一樣通不過編譯。 3)全部的控制結構、函數和方法定義的"i"放到行尾,而不能另起一行。 4)提供go fmt工具格式化代碼,使全部的代碼風格保持統一。 Go支持使用comma,ok表達式 常見的幾個comma,ok 表達式以下。

1. 讀取chan值 讀取已經關閉的通道,不會阻塞,也不會引發panic,而是一直返回該通道的零值。若判斷通道是否已經關閉有兩種方法:一種是讀取通道的comma,ok 表達式,若是通道已經關閉,則ok的返回值是false,另外一種就是經過range循環迭代。看下面的示例:

import "fmt"
func main()  {
   c := make(chan int)

   go func() {
      c <- 1
      c <- 2
      close(c)
   }()
   for{
      v,ok := <-c
      if ok{
         fmt.Println(v)
      }else {
         break
      }
   }

   /* for v := range c{ fmt.Println(v) } */
}
複製代碼
  1. 獲取map值 獲取map中不存在鍵的值不會發生異常,而是會返回值類型的零值,若是想肯定map中是否存在key,則可使用獲取map值的comma,ok語法。示例以下:
import "fmt"
func main()  {
   m := make(map[string]string)

   v,ok := m["test"]
   //經過ok進行判斷
   if !ok{
      fmt.Println("m[test] is nil")
   }else {
      fmt.Println("m[test] =",v)
   }
}
複製代碼
  1. 類型斷言 類型斷言,是Go語言中一個難點。有一點難理解。這一文將不詳細介紹用法,後面將會專門寫一篇文章進行詳細的介紹。 接口斷言一般可使用comma,ok語句來肯定接口是否綁定某個實例類型,或者判斷接口綁定的實例類型是否實現另外一個接口。能夠看src/net/http/request.go中部分代碼以下:
858 rc, ok := body.(io.ReadCloser)
1191 if _, ok := r.Body.(*maxBytesReader); !ok {
複製代碼

好啦,本文到此結束啦,基本對Go語言基於其餘語言的不一樣作了一個介紹,由於我也是一個新手,理解的還不是很到位,也在努力學習中,有錯誤或者有須要更改的地方,請聯繫我,很是感謝。同時再一次推薦個人公衆號:Golang夢工廠,我會不斷髮表關於Golang方面的知識,面試、我的理解等多個方面,必定對你受益不淺的。公衆號搜索:Golang夢工廠,或者直接掃描下方二維碼便可。

在這裏插入圖片描述
相關文章
相關標籤/搜索