9.3 无类型常量消除烦恼,简化代码

现在有下面这些字面值:

5
3.1415926
"Hello, Gopher"
'a'
false

我们从中挑选三个字面值以魔数的形式直接参与变量赋值运算:

type myInt int
type myFloat float32
type myString string

func main() {
    var j myInt = 5
    var f myFloat = 3.1415926
    var str myString = "Hello, Gopher"

    fmt.Println(j)    // 输出:5
    fmt.Println(f)    // 输出:3.1415926
    fmt.Println(str)  // 输出:Hello, Gopher
}

可以看到这三个字面值无须显式类型转换就可以直接赋值给对应的三个自定义类型的变量,这等价于下面的代码:

var j myInt = myInt(5)
var f myFloat = myFloat(3.1415926)
var str myString = myString("Hello, Gopher")

但显然之前的无须显式类型转换的代码更为简洁。

Go的无类型常量恰恰就拥有像字面值这样的特性,该特性使得无类型常量在参与变量赋值和计算过程时无须显式类型转换,从而达到简化代码的目的:

const (
    a  = 5
    pi = 3.1415926
    s  = "Hello, Gopher"
    c  = 'a'
    b  = false
)

type myInt int
type myFloat float32
type myString string

func main() {
    var j myInt = a
    var f myFloat = pi
    var str myString = s
    var e float64 = a + pi

    fmt.Println(j)    // 输出:5
    fmt.Println(f)                // 输出:3.1415926
    fmt.Println(str)              // 输出:Hello, Gopher
    fmt.Printf("%T, %v\n", e, e)  // float64, 8.1415926
}

无类型常量使得Go在处理表达式混合数据类型运算时具有较大的灵活性,代码编写也有所简化,我们无须再在求值表达式中做任何显式类型转换了。

除此之外,无类型常量也拥有自己的默认类型:无类型的布尔型常量、整数常量、字符常量、浮点数常量、复数常量、字符串常量对应的默认类型分别为bool、int、int32(rune)、float64、complex128和string。当常量被赋值给无类型变量、接口变量时,常量的默认类型对于确定无类型变量的类型及接口对应的动态类型是至关重要的。示例如下。

const (
    a = 5
    s = "Hello, Gopher"
)

func main() {
    n := a
    var i interface{} = a

    fmt.Printf("%T\n", n)         // 输出:int
    fmt.Printf("%T\n", i)         // 输出:int
    i = s
    fmt.Printf("%T\n", i)         // 输出:string
}
小结

所有常量表达式的求值计算都可以在编译期而不是在运行期完成,这样既可以减少运行时的工作,也能方便编译器进行编译优化。当操作数是常量时,在编译时也能发现一些运行时的错误,例如整数除零、字符串索引越界等。

无类型常量是Go语言推荐的实践,它拥有和字面值一样的灵活特性,可以直接用于更多的表达式而不需要进行显式类型转换,从而简化了代码编写。此外,按照Go官方语言规范[2]的描述,数值型无类型常量可以提供比基础类型更高精度的算术运算,至少有256 bit的运算精度。


[1]https://tip.golang.org/doc/faq#conversions

[2]https://tip.golang.org/ref/spec#Constants