go语言入门-常量(const和itoa)
定义
常量:常量标识恒定不变的值,区别于变量var。var-变量不赋值存在默认值,但是常量声明是必须显示赋值。
常量关键字:const
常量不能使用 “:=” 语法定义。
声明方式
普通声明方式
const E float64 = 1.602
const EE = 1.602 // 类型可以省略
批量声明方式
常用声明使用方式
const (
A = 'A'
B = 'B'
C = 'C'
)
func main() {
//fmt.Println(E)
fmt.Println(A)
fmt.Println(B)
fmt.Println(C)
/***
output:
65
66
67
*/
}
批量声明语法糖
- 如果没有指定变量类型和初始化值,那么同上一个常量值一样(如果上一个常量没值-则编译报错)
package main
import "fmt"
//const E float64 = 1.602
const (
//O //报错,O前面没有值
A = 'A'
B //使用常量A的值
C //使用常量A的值
)
func main() {
//fmt.Println(E)
fmt.Println(A)
fmt.Println(B)
fmt.Println(C)
/***
output:
65
65
65
*/
}
常量计数器
iota是go语言的常量计数器,因此只能用于常量,也就是域const关键字绑定,可以理解是iota是const语句中行索引【非声明const的行忽略】。iota作用于itoa使用前的最近的const,如果itoa后续后又出现const,则会重置
const AA = iota // 针对当前const 行索引为0
const BB = iota // 新const 行索引为0
const CC = iota // 又是新的const 行索引0
fmt.Println(AA)
fmt.Println(BB)
fmt.Println(CC)
/**
output:
0
0
0
*/
以上案例可以看出 const CONSTNAME [type] = itoa 常量定义只能为0。所以itoa只能在const ( xxx xxx xx) 批量常量声明里有用。如下:
const (
O = iota // 0
P // 1
Q // 2
)
fmt.Println(O)
fmt.Println(P)
fmt.Println(Q)
/**
output:
0
1
2
*/
以下批量常量声明中存在空行,不做itoa计数累计
const (
O = iota // 0
// 没有const变量声明的空行是作为itoa航索引累加的【非常量声明的行忽略】
P // 1
Q // 2
)
fmt.Println(O)
fmt.Println(P)
fmt.Println(Q)
/**
output:
0
1
2
*/
特殊用法
- 使用 ‘_’忽略某一生成的计数器值
const (
_ = iota
b
c
_
e
)
fmt.Println(b, c, e)
/**
output:
1 2 4
*/
- itoa插队(个人理解为覆盖掉itoa的计数值,计数器还在继续)
const (
A = iota
B
C = 100 //插队,
D
)
fmt.Println(A, B, C, D)
/**
output:
0 1 100 100
*/
为啥 D不是3,而是100呢,因为itoa计数在C被赋值100后,后续常量的值以字面值常量100为准,但是itoa计数器还在计数,只是没有赋值给常量,需要显示的通过iota恢复
const (
A = iota
B
C = 100 //插队,覆盖掉iota的 值而已
C1 //使用100赋值,跟随上一个常量C的显示赋值 iota计数并有中指
D = iota //显示恢复iota-赋值
E //因为itoa被没有终止所以5
)
fmt.Println(A, B, C, C1, D, E)
/**
output:
0 1 100 100 4 5
*/
- 多常量并列定义
在同一个常量组里,可以再多个常量定义中使用iota,多个常量按照列单独计数。互不干涉。
const (
aa, a = iota, iota // aa = 0 b=0
bb, _ //bb = 0, _忽略当前计数 增加
cc, c //计数增加
dd, d = iota, 100 //dd = 3, d 被重新赋值为100,继续计数
ee, e = 100, 100 // ee = 100, e = 100, 打断计数复制动作,但是并没有中短计数,
ff, f = iota, iota //ff = 5, f = 5 显式恢复计数赋值
)
fmt.Println(aa, a)
fmt.Println(bb)
fmt.Println(cc, c)
fmt.Println(dd, d)
fmt.Println(ee, e)
fmt.Println(ff, f)
/**
output:
0 0
1
2 2
3 100
100 100
5 5
*/
- 从常量可以是编译时能够确定的表达式
/**
output:
0 0
1
2 2
3 100
100 100
5 5
*/
const (
hello = "hellooooooooooo" //字面值常量
worldLen = len("word") //字面值常量长度编译时确定 4
ptrSize = unsafe.Sizeof(hello) //字符串底层为一个值指向字符串的指正和字符串的长度。因此为16.
/**
type stringStruct struct {
str unsafe.Pointer
len int
}
*/
)
var tmp int = 1
fmt.Println(hello, worldLen, ptrSize)
fmt.Println(unsafe.Sizeof(ptrSize), unsafe.Sizeof(tmp)) //unsafe.Pointer 8个字节, int 8个字节--》所以字符串的unsafe.Sizeof(str) = 16
/**
output:
hellooooooooooo 4 16
8 8
*/
用法
const和itoa模拟枚举
go语言没有关键字enum,一般是通过一组常量(等差、等比-有规则)来模拟实现枚举类型,如下:
const (
Su = iota
Mo
Tu
We
Th
Fr
Sa
)
fmt.Println(Su, Mo, Tu, We, Th, Fr, Sa)
/**
output:
0 1 2 3 4 5 6
*/
实际编码中,建议使用自定义类型实现枚举类型,如下:
const (
SU Week = iota
MO
TU
WE
TH
FR
SA
)
func WeekPrinter(day Week) {
fmt.Printf("type:%T %#v\n", day, day)
}
func main() {
WeekPrinter(1) //1转为Week类型 隐式
WeekPrinter(SU) //
WeekPrinter(101) //101 不能识别取值范围 101也是week的范围内,字面值常量转为Week类型
a := 2
//WeekPrinter(a) //非字面值常量 int类型无法隐式转为week 必须显示转换
WeekPrinter(Week(a))
}
存在的问题:
- itoa模拟枚举,无法限定范围,如上图的WeekPrinter(101) ,也需要了解字面值常量隐式转换,普通变量必须手工显示转换类型。
常量和变量的区别
- 常量是只读,声明赋值后无法修改,变量可以重复修改其内容值。常量通产会被编译器在预处理阶段直接展开,作为指令数据使用。(参考《Go语言学习笔记》)
- 常量在运行时不分配存储地址,变量会分配地址
var m = 1
const n = 10
fmt.Println(&m, m)
fmt.Println(&n, n) //报错:cannot take the address of n