Go语言中的常量使用关键字 const 定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型、数字型(整数型、浮点型和复数)和字符串型。由于编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式。
枚举的实现
在go中,不像c,有原生的enum关键字支持枚举,那枚举怎么实现呢?先看结论,如下代码
package main import ( "fmt" ) const GlobalSingleConst int = 10 const ( GlobalListConst1 int = 1 GlobalListConst2 int = 2 ) // 默认是使用第一个变量的值,也可以使用表达式,如后文 const ( GlobalEnumType1 = 10 // 10 GlobalEnumType2 // 10 GlobalEnumType3 // 10 GlobalEnumType4 // 10 ) // 可以看到这里的变量表达式是隐式重复的,也可以显示的填写(如后文) const ( _ = iota // 0 GlobalEnumTypeC1 = 1 << iota // 1 // 1 GlobalEnumTypeC2 // 2 GlobalEnumTypeC4 // 4 GlobalEnumTypeC5 // 8 ) // 定义一个枚举列表,其中iota是一个内置函数,代表的这个内置函数所在的位次 const ( GlobalEnumTypeA1 = 1 << iota // 1 GlobalEnumTypeA2 = 1 << iota // 2 GlobalEnumTypeA3 = 1 << iota // 4 GlobalEnumTypeA4 = 1 << iota // 8 ) // 我们可以通过 _ 来表示跳过这一行,为后续可能的状态预留位置 const ( GlobalEnumTypeB1 = 1 << iota // 1 GlobalEnumTypeB2 = 1 << iota // 2 _ GlobalEnumTypeB4 = 1 << iota // 8 GlobalEnumTypeB5 = 1 << iota // 16 // 这里我们的空行对 iota的计算没有任何影响 GlobalEnumTypeB6 = 1 << iota * iota // 32*32 ) // 枚举的格式化 type StatusCode uint32 const ( _ = iota StatusCode1 StatusCode = 1 << iota StatusCode2 StatusCode3 StatusCodeError ) func (s StatusCode) String() string { switch s { case StatusCode1: return fmt.Sprintf("Code: %s", "StatusCode1") case StatusCode2: return fmt.Sprintf("Code: %s", "StatusCode1") case StatusCode3: return fmt.Sprintf("Code: %s", "StatusCode1") default: return fmt.Sprintf("Code: %s", "StatusCodeError") } } func main() { var c StatusCode = 10 fmt.Println(StatusCode1) fmt.Println(StatusCode2) fmt.Println(StatusCode3) fmt.Println(c) }
输出:
Code: StatusCode1
Code: StatusCode1
Code: StatusCode1
Code: StatusCodeError
上文列举了const+iota配合的一些例子,用以实现枚举。那么问题就来了,为何go语言要搞的这个麻烦?感觉违反了其号称的"简洁性"。我们可以这样理解他的设计:
首先,"枚举"这个概念体现的是一个程序设计的期望,期望能有一组具有明确、特定的业务含义的状态列表,这些列表不需要修改,更多的情况是作为常量使用,来进行状态判定等操作。
那么,这里就引出来枚举的两个特征:
- 第一:一组变量列表
- 第二:常量属性
这两个特性在go语言中,通过批量const + iota完全可以搞定,没必要再额外的定义出来一个关键字来完成,因为即使有一个关键字,其承载形式、写法、规则可能是大多相似,参考c的写法:
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
如果是用go的const+iota,如下:
type DAY uint8 const ( _ DAY = 1 << iota MON TUE WED THU FRI SAT SUN )
这两者几乎没有区别,并且因为 iota 的加入让go的定义变得更为可靠、易用。
这里引申出另一个问题,iota是个什么?
iota是一个内置函数,其在编译期间生效,用以获取在const列表中变量的index。
2. 实现原理const是怎么实现不可修改这个特性的?
因为const修饰的变量是在编译期间,直接进行的展开,是一个常量,存储在程序的全局区-常量区,而这个区的数据不可寻址、不可修改。因而,被const修饰的变量不可修改。
3. 一些问题对const变量取地址会发生什么?
package main const cl = 100 var bl = 123 func main() { println(&bl,bl) println(&cl,cl) }
常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,存储在全局区-常量区,是没有地址的。因此这个会在编译期间报错。
cannot take the address of cl
#golang工程师#