常量能够说在每一个代码文件中都存在,使用常量有不少好处: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】,共同窗习,一块儿进步~