go 语言常量

Go 语言的常量是一种在源码编译期间被创建的语法元素。这是在说这个元素的值可以像变量那样被初始化,但它的初始化表达式必须是在编译期间可以求出值来的。

而且,Go 常量一旦声明并被初始化后,它的值在整个程序的生命周期内便保持不变。这样,我们在并发设计时就不用考虑常量访问的同步,并且被创建并初始化后的常量还可以作为其他常量的初始表达式的一部分。

go 常量的生命使用的关键词 const 生命常量

const Pi float64 = 3.14159265358979323846 // 单行常量声明

// 以const代码块形式声明常量
const (
    size int64 = 4096
    i, j, s = 13, 14, "bar" // 单行声明多个常量
)

Go 原生支持常量创新

无类型常量

我们知道 Go 语言对类型安全是有严格要求的:即便两个类型拥有着相同的底层类型,但它们仍然是不同的数据类型,不可以被相互比较或混在一个表达式中进行运算。这一要求不仅仅适用于变量,也同样适用于有类型常量(Typed Constant)中

type myInt int

const n myInt = 13
const m int = n + 5 //报错 cannot use n + 5 (type myInt) as type int in const initializer

func main() {
	var a int = 5
	fmt.Println(a + n) //报错invalid operation: a + n (mismatched types int and myInt)
}

对于 type 生成新类型,虽然定义数据格式的是 int 类型 是一样的,对于数据类型来说他们是不同类型的数据,不能直接做数值运算操作,需要做显示类型的转换,把他们的值类型 转换成一直 才能进行计算

type myInt int

const n myInt = 13
const m int = int(n) + 5 // ok

func main() {
	var a int = 5
	fmt.Println(a + int(n)) //ok
}

隐式转型

type myInt int
const n = 13

func main() {
    var a myInt = 5
    fmt.Println(a + n)  // 输出:18
}

这段代码我们看到 n 没有 做显示计算,在fmt.Println(a + n) 也正确运行成功打印了正常的结果,因为 const n = 13 定义n的常量的时候,并没有定义 常量的 数据类型,在运算 a + n 的时候 自动将 n转换成 int 类型(隐士转型) ,a 和 n 的数据类型相同了 可以做计算了

同样的生命变量的时候老师讲过 不能直接计算,需要显示转型才可以计算,为什么常量会支持呢

因为常量的特性 Go 常量一旦声明并被初始化后,它的值在整个程序的生命周期内便保持不变,认为常量是比较安全的数据类型可以支持这种 隐式转型

实现枚举

Go 语言其实并没有原生提供枚举类型。但是 Go 开发者对枚举这种类型的需求是现实存在的呀。那这要怎么办呢?其实,在 Go 语言中,我们可以使用 const 代码块定义的常量集合,来实现枚举。这是因为,枚举类型本质上就是一个由有限数量常量所构成的集合

Go 的 const 声明枚举的两个特性

隐式重复前一个非空表达式”的机制

const (
    Apple, Banana = 11, 22
    Strawberry, Grape 
    Pear, Watermelon 
)

首次声明的常量Apple 和 Banana 值为 11, 22 ,按章他隐身重复前一个的特性 Strawberry, Grape 这两个变量会赋值 上一行 赋相同的值 Pear, Watermelon 这两个常量 也是遵循这一特性 ,如下代码常量的赋值

const (
    Apple, Banana      = 11, 22
    Strawberry, Grape  = 11, 22
    Pear, Watermelon   = 11, 22
)

不过,仅仅是重复上一行显然无法满足“枚举”的要求,因为枚举类型中的每个枚举常量的值都是唯一的。所以,Go 在这个特性的基础上又提供了“神器”:iota,有了 iota,我们就可以定义满足各种场景的枚举常量了。

iota特性

// $GOROOT/src/sync/mutex.go 
const ( 
    mutexLocked = 1 << iota
    mutexWoken
    mutexStarving
    mutexWaiterShift = iota
    starvationThresholdNs = 1e6
)

iota是从偏移值 0 开始算起 mutexLocked 1 << iota 对应值为1
mutexWoken iota 值为1 对应 继续遵循这个公式 1 << iota 对应的值为2
mutexStarving iota 值为2 对应 继续遵循这个公式 1 << iota 对应的值为4
mutexWaiterShift 值为 iota 此时的 iota 值为 3 mutexWaiterShift 值为3
starvationThresholdNs = 1e6 设置了默认值 不在走 iota 规则

iota 是根据 行号 不是根据iota 出现的次数 计算iota 的偏移值的

const (
    Apple, Banana = iota, iota + 10 // 0, 10 (iota = 0)
    Strawberry, Grape // 1, 11 (iota = 1)
    Pear, Watermelon  // 2, 12 (iota = 2)
)

设置枚举是从1 开始,第一行定义 空指示符 占用 0 的值

// $GOROOT/src/syscall/net_js.go
const (
    _ = iota
    IPV6_V6ONLY  // 1
    SOMAXCONN    // 2
    SO_ERROR     // 3
)

每个常量对应的 iota 是独立的相互不受影响,

const (
    a = iota + 1 // 1, iota = 0
    b            // 2, iota = 1
    c            // 3, iota = 2
)

const (
    i = iota << 1 // 0, iota = 0
    j             // 2, iota = 1
    k             // 4, iota = 2
)

每个 iota 的生命周期都始于一个 const 代码块的开始,在该 const 代码块结束时结束。

好记性不如烂笔头 本文学自 极客时间 Tony Bai · Go 语言第一课