GO語言中ni,零值與空結(jié)構(gòu)體的使用
go語言的初學(xué)者,特別是java開發(fā)者新學(xué)習(xí)go語言,對(duì)于一些和java類似但是又有差異的概念很容易混淆,比如說go中的零值,nil 和 空結(jié)構(gòu)體。本文就來詳細(xì)探討一下go中這些特殊概念的含義和實(shí)際場(chǎng)景中的應(yīng)用:
零值
零值(The Zero Value)可以看作為當(dāng)你聲明了一個(gè)變量,但沒有顯式的初始化的時(shí)候,系統(tǒng)為變量賦予的一個(gè)默認(rèn)初始值。官方對(duì)零值的定義如下:
When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for numeric types, “” for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.
據(jù)此我們可總結(jié)出:
值類型 布爾類型為 false, 數(shù)值類型為 0,字符串為”“,數(shù)組和結(jié)構(gòu)體(struct)會(huì)遞歸初始化其元素或字段,即其初始值取決于元素或字段。這里所謂的值類型其實(shí)就相當(dāng)于java中的 primary 類型,只是需要注意的是string在java中是對(duì)象類型,而go中string則是值類型。
引用類型 均為 nil,包括指針 pointer,函數(shù) function,接口 interface,切片 slice,管道 channel,映射 map。
tip: 其實(shí)go里面沒有真正的引用類型,可以粗略的理解為值類型的變量直接存儲(chǔ)值,引用類型的變量存儲(chǔ)的是一個(gè)地址,這個(gè)地址用于存儲(chǔ)最終的值
值類型
因?yàn)橛辛阒档拇嬖?,使得我們?cè)谑褂米兞繒r(shí),大部分情況下可以不必進(jìn)行初始化而直接使用,這樣能夠保持代碼的簡(jiǎn)潔性,也能夠盡量避免出現(xiàn)Java開發(fā)中常見的**NullPointerException,**以下是一些例子:
package main import "sync" type Value struct { mu sync.Mutex //無需初始化,聲明就能用 val int } func (v *Value)Incr(){ defer v.mu.Unlock() v.mu.Lock() v.val++ } func main() { var i Value i.Incr() }
sync.Mutex本質(zhì)上是一個(gè)結(jié)構(gòu)體:
// A Mutex is a mutual exclusion lock. // The zero value for a Mutex is an unlocked mutex. // // A Mutex must not be copied after first use. type Mutex struct { state int32 sema uint32 }
那么如果是引用類型,零值為nil,是不是就不能直接用了呢?這個(gè)實(shí)際上也要分情況,按照類型我們一個(gè)個(gè)來看:
切片(Slices)
切片的零值是一個(gè)nil slice,除了不能按照序號(hào)索引查詢以外,其它的操作都能做:
func testNilSlice() { var nilSlice []string fmt.Println(nilSlice == nil) // true fmt.Println(nilSlice[0]) //index out of range emptySlice = append(nilSlice, "dd") // append操作會(huì)自動(dòng)擴(kuò)容 fmt.Println(nilSlice[0]) //輸出dd }
nil slice與not nil slice的區(qū)別:
type Person { Friends []string } var f1 []string //nil切片 json1, _ := json.Marshal(Person{Friends: f1}) fmt.Printf("%s\n", json1) //output:{"Friends": null} f2 := make([]string, 0) //non-nil空切片 ,等價(jià)于 f2 := []string{} json2, _ := json.Marshal(Person{Friends: f2}) fmt.Printf("%s\n", json2) //output: {"Friends": []}
推薦在日常使用時(shí),沒有特殊需求都使用var nilSlice []string 這樣的形式聲明空切片:https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices
Map
對(duì)于nil的map,我們可以簡(jiǎn)單把它看成是一個(gè)只讀的map,不能進(jìn)行寫操作,否則就會(huì)panic:
func testNilMap() { var m map[string]string fmt.Println(m["key"]) //輸出"" m["key"]="value" //panic: assignment to entry in nil map }
那么nil map有啥用呢,可以看看以下的例子:
func NewGet(url string, headers map[string]string) (*http.Request, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } for k, v := range headers { req.Header.Set(k, v) } return req, nil } //調(diào)用該方法時(shí)如果沒有header,可以傳入一個(gè)空的map,例如: NewGet("http://google.com", map[string]string{}) //也可以直接傳入nil NewGet("http://google.com", nil)
Channel
nil channel會(huì)阻塞對(duì)該channel的所有讀、寫。所以,可以將某個(gè)channel設(shè)置為nil,進(jìn)行強(qiáng)制阻塞,對(duì)于select分支來說,就是強(qiáng)制禁用此分支
func addIntegers(c chan int) { sum := 0 t := time.NewTimer(time.Second) for { select { case input := <-c: sum = sum + input case <-t.C: c = nil fmt.Println(sum) // 輸出10 } } } func main() { c := make(chan int, 1) go addIntegers(c) for i := 0; i <= 10; i++ { c <- i time.Sleep(time.Duration(200) * time.Millisecond) } }
指針(Pointers)
指針如果為nil,則對(duì)指針進(jìn)行解引用的話,會(huì)引發(fā)我們?cè)趈ava中非常熟悉的空指針錯(cuò)誤
type Person struct { Name string Sex string Age int } var p *Person fmt.Println(p.Name) // panic: runtime error: invalid memory address or nil pointer dereference
神奇的nil
nil 是 Golang 中預(yù)先聲明的標(biāo)識(shí)符,其主要用來表示引用類型的零值(指針,接口,函數(shù),映射,切片和通道),表示它們未初始化的值。
// [src/builtin/builtin.go](https://golang.org/src/builtin/builtin.go#L98) // // nil is a predeclared identifier representing the zero value for a // pointer, channel, func, interface, map, or slice type. var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
nil在go語言里面不是一個(gè)關(guān)鍵字或者保留字,所以你可以用nil作為變量名(作死):
var nil = errors.New("my god")
nil沒有默認(rèn)的類型,所以不能給一個(gè)未聲明類型的變量賦值,也不能和自己比較:
a := nil // cannot declare variable as untyped nil: a fmt.Println(nil == nil) // invalid operation: nil == nil (operator == not defined on nil) fmt.Printf("%T", nil) // use of untyped nil
比較nil時(shí)一定要注意nil實(shí)際上是有類型的,不同類型的nil是不相等的,比如下面的例子:
var p *int var i interface{} fmt.Println(p) // <nil> fmt.Println(i) // <nil> fmt.Println(p == i) // false
再看一個(gè)在實(shí)際編碼里面很容易犯的錯(cuò)誤:
type BusinessError struct { error errorCode int64 } func doBusiness() *BusinessError { return nil } func wrapDoBusiness() error { err := doBusiness() return err } func testError() { err := wrapDoBusiness() //這里面拿到的本質(zhì)上是一個(gè)<T:*BusinessError,V:nil>的nil fmt.Println(err == nil) }
建議:如果任何地方有判斷interface是否為 nil 值的邏輯,一定不要寫任何有關(guān)于將interface賦值為具體實(shí)現(xiàn)類型(可能為nil)的代碼,如果是 nil 值就直接賦給interface,而不要過具體類型的轉(zhuǎn)換
type BusinessError struct { error errorCode int64 } func doBusiness() *BusinessError { return nil } func wrapDoBusiness() error { err := doBusiness() if err == nil { return nil //如果返回值為nil,直接返回nil,不要做類型轉(zhuǎn)換 } else { return err } } func testError() { err := wrapDoBusiness() fmt.Println(err == nil) }
空結(jié)構(gòu)體
golang 正常的 struct 就是普通的一個(gè)內(nèi)存塊,必定是占用一小塊內(nèi)存的,并且結(jié)構(gòu)體的大小是要經(jīng)過邊界,長(zhǎng)度的對(duì)齊的,但是“空結(jié)構(gòu)體”是不占內(nèi)存的,size 為 0;
var q struct{} fmt.Println(unsafe.Sizeof(q)) // 0
空結(jié)構(gòu)體 struct{ } 為什么會(huì)存在的核心理由就是為了節(jié)省內(nèi)存。當(dāng)你需要一個(gè)結(jié)構(gòu)體,但是卻絲毫不關(guān)系里面的內(nèi)容,那么就可以考慮空結(jié)構(gòu)體。以下是幾個(gè)經(jīng)典的用法:
map & struct{}
map 和 struct {} 一般的結(jié)合姿勢(shì)是這樣的:
// 創(chuàng)建 map m := make(map[int]struct{}) // 賦值 m[1] = struct{}{} // 判斷 key 鍵存不存在 _, ok := m[1]
一般 map 和 struct {} 的結(jié)合使用場(chǎng)景是:只關(guān)心 key,不關(guān)注值。比如查詢 key 是否存在就可以用這個(gè)數(shù)據(jù)結(jié)構(gòu),通過 ok 的值來判斷這個(gè)鍵是否存在,map 的查詢復(fù)雜度是 O(1) 的,查詢很快。這種方式在部分場(chǎng)景下可以起到類似Java中Set的作用
chan & struct{}
channel 和 struct{} 結(jié)合是一個(gè)最經(jīng)典的場(chǎng)景,struct{} 通常作為一個(gè)信號(hào)來傳輸,并不關(guān)注其中內(nèi)容。chan 本質(zhì)的數(shù)據(jù)結(jié)構(gòu)是一個(gè)管理結(jié)構(gòu)加上一個(gè) ringbuffer ,如果 struct{} 作為元素的話,ringbuffer 就是 0 分配的。
chan 和 struct{} 結(jié)合基本只有一種用法,就是信號(hào)傳遞,空結(jié)構(gòu)體本身攜帶不了值,所以也只有這一種用法啦,一般來說,配合 no buffer 的 channel 使用。
waitc := make(chan struct{}) // ... goroutine 1: // 發(fā)送信號(hào): 投遞元素 waitc <- struct{} // 發(fā)送信號(hào): 關(guān)閉 close(waitc) goroutine 2: select { // 收到信號(hào),做出對(duì)應(yīng)的動(dòng)作 case <-waitc: }
到此這篇關(guān)于GO語言中ni,零值與空結(jié)構(gòu)體的文章就介紹到這了,更多相關(guān)GO ni 零值 空結(jié)構(gòu)體內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言中的空值(nil)與零值(zerovalue)區(qū)別詳解
在Go語言中,空值(nil)和零值(zero value)是兩個(gè)不同的概念,它們?cè)谡Z義、使用場(chǎng)景以及實(shí)際的編程實(shí)踐中有著明顯的區(qū)別,理解這兩者的差異對(duì)于編寫清晰、健壯的Go代碼至關(guān)重要,需要的朋友可以參考下2024-06-06詳解Go語言中new和make關(guān)鍵字的區(qū)別
本篇文章來介紹一道非常常見的面試題,到底有多常見呢?可能很多面試的開場(chǎng)白就是由此開始的。那就是 new 和 make 這兩個(gè)內(nèi)置函數(shù)的區(qū)別,希望對(duì)大家有所幫助2023-03-03Go?語言簡(jiǎn)單實(shí)現(xiàn)Vigenere加密算法
這篇文章主要介紹了Go語言簡(jiǎn)單實(shí)現(xiàn)Vigenere加密算法,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-09-09基于Go?goroutine實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天服務(wù)
對(duì)于聊天服務(wù),想必大家都不會(huì)陌生,因?yàn)樵谖覀兊纳钪薪?jīng)常會(huì)用到,本文我們用?Go?并發(fā)來實(shí)現(xiàn)一個(gè)聊天服務(wù)器,這個(gè)程序可以讓一些用戶通過服務(wù)器向其它所有用戶廣播文本消息,文中通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06