1. 基础用法

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工程师#