go 定义一个结构体并赋初始值_Go中必须谈论的四个迷点
很多熟悉Go的程序員們都會說到Go是一門很簡單的語言,話雖如此,但實際上Go的簡單是基于復雜底層的極簡包裝。
Go在很多地方均做了“隱式”的轉換,這也就導致了很多迷惑點,本文總結了Go開發中幾個令人迷惑的地方,如有不當之處請指正。
nil究竟是什么
首先明確一點:nil是值而非類型。nil值只能賦值給slice、map、chan、interface和指針。
在Go中,任何類型都會有一個初始值。數值類型的初始值為0,slice、map、chan、interface和指針類型的初始值為nil,對于nil值的變量,我們可以簡化理解為初始狀態變量。
但nil在實際使用過程中,仍有不少令人迷惑的地方。
var err error e := &err if e != nil {fmt.Printf("&err is not nil:%pn", e) } // 輸出:&err is not nil:0xc0000301f0err是一個接口類型的變量,其初始值為nil,然后對err進行取址操作會發現能成功取到地址,這就是Go和C++最大的不同之一。有C++基礎的人在剛接觸Go的時候,自然而然的會認為nil是個空指針類型值,上面的代碼力證在Go中,nil只是一個表示初始狀態的值。
對于slice、map、chan、interface,當值為nil時,不具備可寫性。
// 1 var s []int fmt.Printf("%vn", s[0]) // 輸出panic// 2 var c chan int val := <-c fmt.Printf("%vn", val) // 輸出panic// 3 var m map[int]int m[1] = 123 // 輸出panic上面3段代碼均會出現panic,對于slice、map、chan類型的nil值變量,可以理解為可讀不可寫,只有通過make(new)創建的對象實例滿足可寫性。
接口的本質
Go官方文檔中表示:interface本身是引用類型,即接口類型本身是指針類型。
type Animal interface {Barking() }type Cat struct { }func (c *Cat) Barking() {fmt.Printf("Meow~~n") }type Dog struct{}func (d Dog) Barking() {fmt.Printf("W~W~W~n") }Cat和Dog類型都實現了Barking接口,需要注意的是,Cat是以指針接收器方式實現Barking接口,Dog是以值傳遞方式實現Barking接口。在Go中,當調用接口方法時,會自動對指針進行解引用。下面的代碼可以證明這一點:
d := &Dog{} d.Barking()c := Cat{} c.Barking() /* 輸出:W~W~W~Meow~~ */接口的作為函數參數如何傳遞?
func AnimalBarking(a Animal) {a.Barking() }根據上面這段代碼,如何調用AnimalBarking方法呢? 首先明確Animal是引用類型(指針),由于接口會自動對傳遞的指針進行解引用,所以當接口類型作為函數參數傳遞時,有以下規則: 當以指針接收器實現接口方法時,傳遞AnimalBarking的參數必須為對象指針。 當以對象接收器實現接口方法時,傳遞AnimalBarking的參數既可以是對象指針(指針會自動解引用),也可以是對象實例。
下面的代碼合法:
d1 := &Dog{} AnimalBarking(d1)d2 := Dog{} AnimalBarking(d2)很多地方都專門指出:指向接口的指針是無意義的。事實是否真的是這樣子?
我們知道在Go中有專門的接口類型,接口類型在runtime中大概是這樣:
type iface struct {tab *itab // 8bytesdata unsafe.Pointer // 8bytes }對于接口而言,可能會存在這樣的代碼:
type Handler interface {Func() }type Server struct{}func (s *Server) Func() {fmt.Printf("*Server.Funcn") }func Func(handler *Handler) {handler.Func() }上面的代碼在Go1.13下無法通過編譯:handler.Func undefined (type *Handler is pointer to interface, not interface)。 這里要清楚,指向結構的指針和指向接口的指針是兩回事,接口直接存放了結構的類型信息以及結構指針。在Go中,無法為實現了接口方法的struct生成指向接口的指針并調用接口方法。
但當修改為如下代碼則可編譯通過:
func Func(handler *Handler) {if handler != nil && (*handler) != nil {(*handler).Func()} }從Go設計角度出發的。Go里面的interface是個結構,保存了類型信息和數據拷貝,所謂的無意義我更多是認為Go設計上的理念,因為nil值的存在且又是指針,那么問題就回落到有無辦法一步判斷指針和值是否合法。顯然這種代碼有違Go的設計初衷,但指向接口的指針這種東西確實存在于Go中,對此的解釋在這里:https://golang.org/doc/faq#pointer_to_interface
【指向接口的指針是無意義的】這句話確實有點絕對,但確實是存在且意義并不大的東西。
關于接口的延申閱讀:Go interface
defer機制
在Go中提供defer這樣優雅的函數退出后“收尾”操作,但很多人會忽略defer機制中的一點:defer在聲明時引用到的變量就已被“固定”下來。下面的代碼:
var ErrNotFound error = errors.New("Not found") func TestDefer1() error {var err errordefer fmt.Printf("TestDefer1 err: %vn", err)// ...err = ErrNotFoundreturn err }/* 輸出:TestDefer1 err: <nil> */當defer聲明func時,情況不一樣了:
func TestDefer2() error {var err errordefer func() {fmt.Printf("TestDefer2 err: %vn", err)}()// ...err = ErrNotFoundreturn err }/* 輸出:TestDefer2 err: Not found */所以:當defer在聲明語句時引用到的變量就已被實時編譯。
讀寫chan是否應該加鎖
先說答案:不需要。具體原因可以從runtime/chan.go中知道。chan的原始struct如下:
type hchan struct {qcount uint // total data in the queuedataqsiz uint // size of the circular queuebuf unsafe.Pointer // points to an array of dataqsiz elementselemsize uint16closed uint32elemtype *_type // element typesendx uint // send indexrecvx uint // receive indexrecvq waitq // list of recv waiterssendq 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 }從chan的struct定義上來看,有lock字段,再來看看chan的讀寫實現(簡化代碼):
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {// ...lock(&c.lock)// ...unlock(&c.lock)// ... }func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {// ...lock(&c.lock)// ...unlock(&c.lock)// ... }從chan的實現源代碼看到,其讀寫內部均加了鎖,實際上在關閉chan時內部也是加鎖了,所以實際應用中,多個coroutine同時讀寫chan時不需要加鎖。
總結
以上是生活随笔為你收集整理的go 定义一个结构体并赋初始值_Go中必须谈论的四个迷点的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java中super()_Java 泛型
- 下一篇: 可视化_仓库管理可视化