日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

go 定义一个结构体并赋初始值_Go中必须谈论的四个迷点

發布時間:2025/3/21 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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:0xc0000301f0

err是一個接口類型的變量,其初始值為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中必须谈论的四个迷点的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。