3.6 常量
常量是一种表达式,其可以保证在编译阶段就计算出表达式的值,并不需要等到运行时,从而使编译器得以知晓其值。所有常量本质上都属于基本类型:布尔型、字符串或数字。
常量的声明定义了具名的值,它看起来在语法上与变量类似,但该值恒定,这防止了程序运行过程中的意外(或恶意)修改。例如,要表示数学常量,像圆周率,在Go程序中用常量比变量更适合,因其值恒定不变:
与变量类似,同一个声明可以定义一系列常量,这适用于一组相关的值:
许多针对常量的计算完全可以在编译时就完成,以减免运行时的工作量并让其他编译器优化得以实现。某些错误通常要在运行时才能检测到,但如果操作数是常量,编译时就会报错,例如整数除以0,字符串下标越界,以及任何产生无限大值的浮点数运算。
对于常量操作数,所有数学运算、逻辑运算和比较运算的结果依然是常量,常量的类型转换结果和某些内置函数的返回值,例如len、cap、real、imag、complex和unsafe.Sizeof,同样是常量。
因为编译器知晓其值,常量表达式可以出现在涉及类型的声明中,具体而言就是数组类型的长度:
常量声明可以同时指定类型和值,如果没有显式指定类型,则类型根据右边的表达式推断。下例中,time.Duration是一种具名类型,其基本类型是int64,time.Minute也是基于int64的常量。下面声明的两个常量都属于time.Duration类型,通过%T展示:
若同时声明一组常量,除了第一项之外,其他项在等号右侧的表达式都可以省略,这意味着会复用前面一项的表达式及其类型。例如:
如果复用右侧表达式导致计算结果总是相同,这就并不太实用。假若该结果可变该怎么办呢?我们来看看iota。
3.6.1 常量生成器iota
常量的声明可以使用常量生成器iota,它创建一系列相关值,而不是逐个值显式写出。常量声明中,iota从0开始取值,逐项加1。
下例取自time包,它定义了Weekday的具名类型,并声明每周的7天为该类型的常量,从Sunday开始,其值为0。这种类型通常称为枚举型(enumeration,或缩写成enum)。
上面的声明中,Sunday的值为0,Monday的值为1,以此类推。
更复杂的表达式也可使用iota,借用net包的代码举例如下,无符号整数最低5位数中的每一个都逐一命名,并解释为布尔值。
随着iota递增,每个常量都按1<<iota赋值,这等价于2的连续次幂,它们分别与单个位对应。若某些函数要针对相应的位执行判定、设置或清除操作,就会用到这些常量。
下例更复杂,声明的常量表示1024的幂。
然而,iota机制存在局限。比如,因为不存在指数运算符,所以无从生成更为人熟知的1000的幂(KB、MB等)。
练习3.13:用尽可能简洁的方法声明从KB、MB直到YB的常量。
3.6.2 无类型常量
Go的常量自有特别之处。虽然常量可以是任何基本数据类型,如int或float64,也包括具名的基本类型(如time.Duration),但是许多常量并不从属某一具体类型。编译器将这些从属类型待定的常量表示成某些值,这些值比基本类型的数字精度更高,且算术精度高于原生的机器精度。可以认为它们的精度至少达到256位。从属类型待定的常量共有6种,分别是无类型布尔、无类型整数、无类型文字符号、无类型浮点数、无类型复数、无类型字符串。
借助推迟确定从属类型,无类型常量不仅能暂时维持更高的精度,与类型已确定的常量相比,它们还能写进更多表达式而无需转换类型。比如,上例中ZiB和YiB的值过大,用哪种整型都无法存储,但它们都是合法常量并且可以用在下面的表达式中:
再例如,浮点型常量math.Pi可用于任何需要浮点值或复数的地方:
若常量math.Pi一开始就确定从属于某具体类型,如float64,就会导致结果的精度下降。另外,假使最终需要float32值或complex128值,则可能需要转换类型:
字面量的类型由语法决定。0、0.0、0i和'\u0000'全都表示相同的常量值,但类型相异,分别是:无类型整数、无类型浮点数、无类型复数和无类型文字符号。类似地,true和false是无类型布尔值,而字符串字面量则是无类型字符串。
根据除法运算中操作数的类型,除法运算的结果可能是整型或浮点型。所以,常量除法表达式中,操作数选择不同的字面写法会影响结果:
只有常量才可以是无类型的。若将无类型常量声明为变量(如下面的第一条语句所示),或在类型明确的变量赋值的右方出现无类型常量(如下面的其他三条语句所示),则常量会被隐式转换成该变量的类型。
上述语句与下面的语句等价:
不论隐式或显式,常量从一种类型转换成另一种,都要求目标类型能够表示原值。实数和复数允许舍入取整:
变量声明(包括短变量声明)中,假如没有显式指定类型,无类型常量会隐式转换成该变量的默认类型,如下例所示:
注意各类型的不对称性:无类型整数可以转换成int,其大小不确定,但无类型浮点数和无类型复数被转换成大小明确的float64和complex128。Go语言中,只有大小不明确的int类型,却不存在大小不确定的float类型和complex类型,原因是,如果浮点型数据的大小不明,就很难写出正确的数值算法。
要将变量转换成不同的类型,我们必须将无类型常量显式转换为期望的类型,或在声明变量时指明想要的类型,如下例所示:
在将无类型常量转换为接口值时(见第7章),这些默认类型就分外重要,因为它们决定了接口值的动态类型。
至此,我们已经概述了Go的基本数据类型。下一步就是要说明如何将它们构建成为更大的聚合体,如数组和结构体,更进一步组成数据结构以解决实际的编程问题。第4章以此为主题。