go語言定義零值可用的類型學習教程
1. Go 類型的零值
作為 C 程序員出身的我,我總是喜歡用在使用 C 語言的”受過的苦“與 Go 語言中得到的”甜頭“做比較,從而來證明 Go 語言設計者在當初設計 Go 語言時是做了充分考量的。
在 C99 規(guī)范中,有一段是否對棧上局部變量進行自動清零初始化的描述:
如果未顯式初始化且具有自動存儲持續(xù)時間的對象,則其值是不確定的。
規(guī)范的用語總是晦澀難懂的。這句話大致的意思就是:如果是在棧上分配的局部變量,且在聲明時未對其進行顯式初始化,那么這個變量的值是不確定的。比如:
// varinit.c #include <stdio.h> static int cnt; void f() { int n; printf("local n = %d\n", n); if (cnt > 5) { return; } cnt++; f(); } int main() { f(); return 0; }
編譯上面的程序并執(zhí)行:
// 環(huán)境 centos linux gcc 版本 4.1.2 // 注意:在您的環(huán)境中執(zhí)行上述代碼,輸出的結果很大可能與這里有所不同 $ gcc varinit.c $ ./a.out local n = 0 local n = 10973 local n = 0 local n = 52 local n = 0 local n = 52 local n = 52
我們看到分配在棧上的未初始化變量的值是不確定的,雖然一些編譯器的較新版本也都提供一些命令行參數(shù)選項用于對棧上變量進行零值初始化,比如 GCC 就提供如下命令行選項:
-finit-local-zero -finit-derived -finit-integer=n -finit-real=<zero|inf|-inf|nan|snan> -finit-logical=<true|false> -finit-character=n
但這并不能改變 C 語言原生不支持對未顯式初始化局部變量進行零值初始化的事實。資深 C 程序員是深知這個陷阱帶來的問題是有多嚴重的。因此同樣出身于 C 語言的 Go 設計者們在 Go 中徹底對這個問題進行的修復和優(yōu)化。根據(jù)Go 語言規(guī)范:
當通過聲明或調用new為變量分配存儲空間,或者通過復合文字字面量或make調用創(chuàng)建新值,
并且還不提供顯式初始化的情況下,Go會為變量或值提供默認值。
Go 語言的每種原生類型都有其默認值,這個默認值就是這個類型的零值。下面是 Go 規(guī)范定義的內(nèi)置原生類型的默認值(零值)。
所有整型類型:0
浮點類型:0.0
布爾類型:false
字符串類型:""
指針、interface、slice、channel、map、function:nil
另外 Go 的零值初始是遞歸的,即諸如數(shù)組、結構體等類型的零值初始化就是對其組成元素逐一進行零值初始化。
2. 零值可用
我們現(xiàn)在知道了 Go 類型的零值,接下來我們來說“可用”。
Go 從誕生以來就秉承著盡量保持“零值可用”的理念,我們來看兩個例子。
第一個例子是關于 slice 的:
var zeroSlice []int zeroSlice = append(zeroSlice, 1) fmt.Println(zeroSlice) // 輸出:[1]
我們聲明了一個 []int 類型的 slice:zeroSlice,我們并沒有對其進行顯式初始化,這樣 zeroSlice 這個變量被 Go 編譯器置為零值:nil。按傳統(tǒng)的思維,對于值為 nil 這樣的變量我們要給其賦上合理的值后才能使用。但是 Go 具備零值可用的特性,我們可以直接對其使用 append 操作,并且不會出現(xiàn)引用 nil 的錯誤。
第二個例子是通過 nil 指針調用方法的:
// callmethodthroughnilpointer.go package main import ( "fmt" "net" ) func main() { var p *net.TCPAddr fmt.Println(p) //輸出:<nil> }
我們聲明了一個 net.TCPAddr 的指針變量,我們并未對其顯式初始化,指針變量 p 會被 Go 編譯器賦值為 nil。我們在標準輸出上輸出該變量,fmt.Println 會調用 p.String()。我們來看看 TCPAddr 這個類型的 String 方法實現(xiàn):
// $GOROOT/src/net/tcpsock.go func (a *TCPAddr) String() string { if a == nil { return "<nil>" } ip := ipEmptyString(a.IP) if a.Zone != "" { return JoinHostPort(ip+"%"+a.Zone, itoa(a.Port)) } return JoinHostPort(ip, itoa(a.Port)) }
我們看到 Go 標準庫在定義 TCPAddr 類型以及其方法時充分考慮了“零值可用”的理念,使得通過值為 nil 的 TCPAddr 指針變量依然可以調用 String 方法。
在 Go 標準庫和運行時代碼中還有很多踐行“零值可用”理念的好例子,最典型的莫過于 sync.Mutex 和 bytes.Buffer 了。
我們先來看看 sync.Mutex。在 C 語言中,如果我們要使用線程互斥鎖,我們需要這么做:
pthread_mutex_t mutex; // 不能直接使用 // 必須先進行初始化 pthread_mutex_init (&mutex, NULL); // 然后才能執(zhí)行l(wèi)ock或unlock pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex);
但是在 Go 語言中,我們只需這么做:
var mu sync.Mutex mu.Lock() mu.Unlock()
Go 標準庫的設計者很“貼心”地將 sync.Mutex 結構體的零值狀態(tài)設計為可用狀態(tài),這樣讓 Mutex 的調用者可以“省略”對 Mutex 的初始化而直接使用 Mutex。
Go 標準庫中的 bytes.Buffer 亦是如此:
// bytesbufferwrite.go package main import ( "bytes" ) func main() { var b bytes.Buffer b.Write([]byte("Effective Go")) fmt.Println(b.String()) // 輸出:Effective Go }
我們看到我們無需對 bytes.Buffer 類型的變量 b 進行任何顯式初始化即可直接通過 b 調用其方法進行寫入操作,這源于 bytes.Buffer 底層存儲數(shù)據(jù)的是同樣支持零值可用策略的 slice 類型:
// $GOROOT/src/bytes/buffer.go // A Buffer is a variable-sized buffer of bytes with Read and Write methods. // The zero value for Buffer is an empty buffer ready to use. type Buffer struct { buf []byte // contents are the bytes buf[off : len(buf)] off int // read at &buf[off], write at &buf[len(buf)] lastRead readOp // last read operation, so that Unread* can work correctly. }
3. 小結
Go 語言零值可用的理念給內(nèi)置類型、標準庫的使用者帶來很多便利。不過 Go 并非所有類型都是零值可用的,并且零值可用也是有一定限制的,比如:slice 的零值可用不能通過下標形式操作數(shù)據(jù):
var s []int s[0] = 12 // 報錯! s = append(s, 12) // OK
另外像 map 這樣的內(nèi)置類型也沒有提供零值可用的支持:
var m map[string]int m["tonybai"] = 1 // 報錯! m1 := make(map[string]int m1["tonybai"] = 1 // OK
另外零值可用的類型要注意盡量避免值拷貝:
var mu sync.Mutex mu1 := mu // Error: 避免值拷貝 foo(mu) // Error: 避免值拷貝
我們可以通過指針方式傳遞類似 Mutex 這樣的類型。
對于我們 Go 開發(fā)者而言,保持與 Go 一致的理念,給自定義的類型一個合理的零值,并堅持保持自定義類型是零值可用的,這樣我們的 Go 代碼會表現(xiàn)的更加符合 Go 慣用法。
以上就是go語言定義零值可用的類型的詳細內(nèi)容,更多關于go 零值可用類型的資料請關注腳本之家其它相關文章!
相關文章
GO語言字符串處理Strings包的函數(shù)使用示例講解
這篇文章主要為大家介紹了GO語言字符串處理Strings包的函數(shù)使用示例講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04go 判斷兩個 slice/struct/map 是否相等的實例
這篇文章主要介紹了go 判斷兩個 slice/struct/map 是否相等的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12更換GORM默認SQLite驅動出現(xiàn)的問題解決分析
這篇文章主要為大家介紹了更換GORM默認SQLite驅動出現(xiàn)的問題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01