常量能夠說在每一個代碼文件中都存在,使用常量有不少好處:git
Go 語言也提供了常量的語法支持,與其餘語言提供的常量基本一致。可是 Go 中的常量有幾個有用的特性值得了解一下。github
Go 語言中使用const
關鍵字定義常量:golang
package main import "fmt" const PI float64 = 3.1415926 const MaxAge int = 150 const Greeting string = "hello world" func main() { fmt.Println(PI) fmt.Println(MaxAge) fmt.Println(Greeting) }
多個常量定義能夠合併在一塊兒,如上面的幾個常量定義能夠寫成下面的形式:數組
const ( PI float64 = 3.1415926 MaxAge int = 150 Greeting string = "hello world" )
不過一般建議將相同類型的,相關聯的常量定義在一個組裏面。微信
Go 語言中常量有一個很大的限制:只能定義基本類型的常量,即布爾類型(bool
),整數(無符號uint/uint8/uint16/uint32/uint64/uintptr
,有符號int/int8/int16/int32/int64
),浮點數(單精度float32
,雙精度float64
),或者底層類型是這些基本類型的類型。不能定義切片,數組,指針,結構體等這些類型的常量。例如,byte
底層類型爲uint8
,rune
底層類型爲int32
,見 Go 源碼builtin.go
:學習
// src/builtin/builtin.go type byte = uint8 type rune = int32
故能夠定義類爲byte
或rune
的常量:優化
const b byte = 128 const r rune = 'c'
定義其餘類型的變量會在編譯期報錯:ui
type User struct { Name string Age int } const u User = User{} // invalid const type User var i int = 1 const p *int = &i // invalid const type *int
iota
Go 語言的代碼中常量定義常用iota
,下面看幾個 Go 的源碼。設計
標準庫time
源碼:指針
// src/time/time.go type Month int const ( January Month = 1 + iota February March April May June July August September October November December ) type Weekday int const ( Sunday Weekday = iota Monday Tuesday Wednesday Thursday Friday Saturday )
標準庫net/http
源碼:
// src/net/http/server.go type ConnState int const ( StateNew ConnState = iota StateActive StateIdle StateHijacked StateClosed )
iota
是方便咱們定義常量的一個機制。簡單來講,iota
獨立做用於每個常量定義組中(單獨出現的每一個const
語句都算做一個組),iota
出如今用於初始化常量值的常量表達式中,iota
的值爲它在常量組中的第幾行(從 0 開始)。使用iota
定義的常量下面能夠省略類型和初始化表達式,這時會沿用上一個定義的類型和初始化表達式。咱們看幾組例子:
const ( One int = iota + 1 Two Three Four Five )
這個也是最常使用的方式,iota
出如今第幾行,它的值就是多少。上面常量定義組中,One
在第 0 行(注意從 0 開始計數),iota
爲 0,因此One = 0 + 1 = 1
。
下一行Two
省略了類型和初始化表達式,所以Two
沿用上面的類型int
,初始化表達式也是iota + 1
。可是此時是定義組中的第 1 行,iota
的值爲 1,因此Two = 1 + 1 = 2
。
再下一行Three
也省略了類型和初始化表達式,所以Three
沿用了Two
進而沿用了One
的類型int
,初始化表達式也是iota + 1
,可是此時是定義的第 2 行,因此Three = 2 + 1 = 3
。以此類推。
咱們能夠在很是複雜的初始化表達式中使用iota
:
const ( Mask1 int = 1<<(iota+1) - 1 Mask2 Mask3 Mask4 )
按照上面的分析Mask1~4
依次爲 1, 3, 7, 15。
另外還有奇數,偶數:
const ( Odd1 = 2*iota + 1 Odd2 Odd3 ) const ( Even1 = 2 * (iota + 1) Even2 Even3 )
在一個組中,iota
不必定出如今第 0 行,可是它出如今第幾行,值就爲多少:
const ( A int = 1 B int = 2 C int = iota + 1 D E )
上面iota
出如今第 2 行(從 0 開始),C
的值爲2 + 1 = 3
。D
和E
分別爲 4, 5。
必定要注意iota
的值等於它出如今組中的第幾行,而非它的第幾回出現。
能夠經過賦值給空標識符來忽略值:
const ( _ int = iota A // 1 B // 2 C // 3 D // 4 E // 5 )
說了這麼多iota
的用法,那麼爲何要用iota
呢?換句話說,iota
有什麼優勢?我以爲有兩點:
iota
的定義,只須要調整位置便可,不須要修改初始化式,由於就沒有寫。增長和刪除也是同樣的,若是咱們一個個寫出了初始化式,刪除中間某個,後續的值就必須作調整。例如,net/http
中的源碼:
type ConnState int const ( StateNew ConnState = iota StateActive StateIdle StateHijacked StateClosed )
若是咱們須要增長一個常量,表示正在關閉的狀態。如今只須要寫出新增的狀態名:
type ConnState int const ( StateNew ConnState = iota StateActive StateIdle StateHijacked StateClosing // 新增的狀態 StateClosed )
若是是顯式寫出初始化式:
type ConnState int const ( StateNew ConnState = 0 StateActive ConnState = 1 StateIdle ConnState = 2 StateHijacked ConnState = 3 StateClosed ConnState = 4 )
這時新增須要改動後續的值。另外須要鍵入的字符也多了很多😊:
const ( StateNew ConnState = 0 StateActive ConnState = 1 StateIdle ConnState = 2 StateHijacked ConnState = 3 StateClosing ConnState = 4 StateClosed ConnState = 5 )
Go 語言中有一種特殊的常量,即無類型常量。即在定義時,咱們不顯式指定類型。這種常量能夠存儲超過常規的類型範圍的值:
package main import ( "fmt" "math" "reflect" ) const ( Integer1 = 1000 Integer2 = math.MaxUint64 + 1 Float1 = 1.23 Float2 = 1e100 Float3 = 1e400 ) func main() { fmt.Println("integer1=", Integer1, "type", reflect.TypeOf(Integer1).Name()) // 編譯錯誤 // fmt.Println("integer2=", Integer2, "type", reflect.TypeOf(Integer2).Name()) fmt.Println("integer2/10=", Integer2/10, "type", reflect.TypeOf(Integer2/10).Name()) fmt.Println("float1=", Float1, "type", reflect.TypeOf(Float1).Name()) fmt.Println("float2=", Float2, "type", reflect.TypeOf(Float2).Name()) // 編譯錯誤 // fmt.Println("float3=", Float3, "type", reflect.TypeOf(Float3).Name()) fmt.Println("float3/float2=", Float3/Float2, "type", reflect.TypeOf(Float3/Float2).Name()) }
雖然無類型常量能夠存儲超出正常類型範圍的值,而且能夠相互之間作算術運算,可是它在使用時(賦值給變量,做爲參數傳遞)仍是須要轉回正常類型。若是值超過正常類型的範圍,編譯就會報錯。每一個無類型常量都有一個默認類型,整數的默認類型爲int
,浮點數(有小數點或者使用科學計數法表示的都被當成浮點數)的默認類型爲float64
。因此上面例子中,咱們定義Integer2
爲無類型常量,值爲uint64
的最大值 + 1,這是容許的。可是若是咱們直接輸出Integer2
的值,就會致使編譯報錯,由於Integer2
默認會轉爲int
類型,而它存儲的值超過了int
的範圍了。另外一方面,咱們能夠用Integer2
作運算,例如除以 10,獲得的值在int
範圍內,能夠輸出。(我使用的是 64 位機器)
下面的浮點數類型也是相似的,Float3
超出了float64
的表示範圍,故不能直接輸出。可是Float3/Float2
的結果在float64
的範圍內,可使用。
上面程序輸出:
integer1= 1000 type int integer2/10= 1844674407370955161 type int float1= 1.23 type float64 float2= 1e+100 type float64 float3/float2= 1e+300 type float64
由輸出也能夠看出整數和浮點的默認類型分別爲int
和float64
。
結合iota
和無類型常量咱們能夠定義一組存儲單位:
package main import "fmt" const ( _ = iota KB = 1 << (10 * iota) MB // 2 ^ 20 GB // 2 ^ 30 TB // 2 ^ 40 PB // 2 ^ 50 EB // 2 ^ 60 ZB // 2 ^ 70,1180591620717411303424 YB // 2 ^ 80 ) func main() { fmt.Println(YB / ZB) fmt.Println("1180591620717411303424 B = ", 1180591620717411303424/ZB, "ZB") }
ZB
實際上已經達到 1180591620717411303424,超過了int
的表示範圍了,可是咱們仍然能夠定義ZB
和YB
,還能在使用時對他們進行運算,只要最終要使用的值在正常類型的範圍內便可。
本文介紹了常量的相關知識,記住兩個要點便可:
iota
的值等於它出如今常量定義組的第幾行(從 0 開始);利用無類型常量,咱們能夠在編譯期對大數進行算術運算。
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~