GO语言基础篇(十一)- 常量详解

359 阅读4分钟

这是我参与8月更文挑战的第 11 天,活动详情查看: 8月更文挑战

常量

常量是一种表达式,其可以保证在编译阶段就计算出表达式的值,并不需要等到运行时,从而使编译器得以知晓其值。所有常量本质上都属于基本类型:布尔型、字符串或数字

常量的声明定义了具名的值,它看起来在语法上与变量类似,但该值恒定,这防止了程序运行过程中的意外(或恶意)修改。例如,要表示数学常量,像圆周率,在Go程序中用常量比变量更适合,因其值恒定不变

const pi = 3.14159 //近似数;math.Pi是更精准的近似

与变量类似,同一个声明,可以定义一系列常量,这适用于一组相关的值:

const (
     e  = 2.71828182845904523536028747135266249775724709369995957496696763
     pi = 3.14159265358979323846264338327950288419716939937510582097494459
)

许多针对常量的计算完全可以在编译时就完成,以减免运行时的工作量。某些错误通常要在运行时才能检测到,但如果操作数是常量,编译时就会报错,例如整数除以0,字符串下标越界,以及任何产生无限大值的浮点数运算

因为常量在编译时就知道其值,所以它可以出现在涉及类型的声明中,比如数组类型的长度

const IPv4Len = 4

// parseIPv4函数解释一个IPv4地址(d・d.d・d)
func parseIPv4(s string) IP {
   var p [IPv4Len]byte
	 //…
}

常量声明可以同时指定类型和值,如果没有显式指定类型,则类型根据右边的表达式推断。下例中,time.Duration是一种具名类型,其基本类型是int64,time.Minute也是基于int64的常量。下面声明的两个常量都属于time.Duration类型,通过%T展示(如果不记得输出格式的占位符含义,点这里

const noDelay time.Duration = 0
const timeout = 5 * time.Minute
fmt.Printf("%T %[1]v\n", noDelay) // time.Duration 0
fmt.Printf("%T %[1]v\n", timeout) // time.Duration 5m0s
fmt.Printf("%T %[1]v\n", time.Minute) // time.Duration 1m0s

若同时声明一组常量,除了第一项之外,其他项在等号右侧的表达式都可以省略,这意味着会复用前面一项的表达式及其类型。例如

const (
    a = 1 
    b
    c = 2 
    d
)
fmt.PrintlnCa, b, c, d) // 1 1 2 2

如果复用右侧表达式导致计算结果总是相同,这就并不太实用。假若该结果可变该怎么 办呢?这就需要用到常量生成器iota

常量生成器iota

常量的声明可以使用常量生成器iota,它创建一系列相关值,而不是逐个值显式写出。常量声明中,iota从0开始取值,逐项加1

type weekday int
const (
    Sunday weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

以上声明中,Sunday = 0,Monday = 1,以此类推

然后看一个稍微复杂一点的

const (
    _ = 1 << (10*iota)
    KiB  // 1024
    MiB  // 1048576 
    GiB  // 1073741824
    TiB  // 1099511627776  (超过1 << 32)
    PiB  // ...
    EiB  // ...
    ZiB  // ...(超过1 << 64)
    YiB  // ...

)

iota存在限制,比如,因为不存在指数运算符,所以无法生成更为人熟知的1000的幂

无类型常量

Go的常量自有特别之处。虽然常量可以是任何基本数据类型,如int或float64,也包括具名的基本类型(如time.Duration),但是许多常量并不从属某一具体类型。编译器将这些从属类型待定的常量表示成某些值,这些值比基本类型的数字精度更高,且算术精度高于原生的机器精度。可以认为它们的精度至少达到256位。从属类型待定的常量共有6种,分别是无类型布尔无类型整数无类型文字符号无类型浮点数无类型复数无类型字符串

说明:只有常量才可以是无类型的。若将无类型常量声明为变量或在类型明确的变量赋值的右侧出现无类型常量,则会被隐式转换成该变量的类型

借助推迟确定从属类型,无类型常量不仅能暂时维持更高的精度,与类型已确定的常量相比,它们还能写进更多表达式而无需转换类型。比如,上例中ZiB和YiB的值过大,用哪种整型都无法存储,但它们都是合法常量并且可以用在下面的表达式中

fmt.Println(YiB/ZiB) // "1024"

再例如,浮点型常量math.Pi可用于任何需要浮点值或复数的地方

var x float32 = math.Pi
var y float64 = math.Pi
var z complexl28 = math.Pi

若常量math.Pi一开始就确定从属于某具体类型,如float64,就会导致结果的精度下 降。另外,假使最终需要float32值或complexl28值,则可能需要转换类型

const Pi64 float64 = math.Pi

var x float32 = float32(Pi64)
var y float64 = Pi64
var z complexl28 = complexl28(Pi64)

注意各类型的不对称性:无类型整数可以转换成int,其大小不确定,但无类型浮点数和无类型复数被转换成大小明确的float64和complexl28。Go语言中,只有大小不明确的 int类型,却不存在大小不确定的float类型和complex类型,原因是,如果浮点型数据的大小不明,就很难写出正确的数值算法

要将变量转换成不同的类型,我们必须将无类型常量显式转换为期望的类型,或在声明 变量时指明想要的类型,如下例所示

var i = int8(0)
var i int8 = 0

参考

《Go程序设计语言》—-艾伦 A. A. 多诺万

《Go语言学习笔记》—-雨痕