gopl-zh.github.com/ch3/ch3-06-2.md

5.2 KiB
Raw Blame History

3.6.2. 无类型常量

Go语言的常量有个不同寻常之处。虽然一个常量可以有任意有一个确定的基础类型例如int或float64或者是类似time.Duration这样命名的基础类型但是许多常量并没有一个明确的基础类型。编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术运算你可以认为至少有256bit的运算精度。这里有六种未明确类型的常量类型分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。

通过延迟明确常量的具体类型无类型的常量不仅可以提供更高的运算精度而且可以直接用于更多的表达式而不需要显式的类型转换。例如例子中的ZiB和YiB的值已经超出任何Go语言中整数类型能表达的范围但是它们依然是合法的常量而且可以像下面常量表达式依然有效译注YiB/ZiB是在编译期计算出来的并且结果常量是1024是Go语言int变量能有效表示的

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

另一个例子math.Pi无类型的浮点数常量可以直接用于任意需要浮点数或复数的地方

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

如果math.Pi被确定为特定类型比如float64那么结果精度可能会不一样同时对于需要float32或complex128类型值的地方则会强制需要一个明确的类型转换

const Pi64 float64 = math.Pi

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

对于常量面值不同的写法可能会对应不同的类型。例如0、0.0、0i和'\u0000'虽然有着相同的常量值但是它们分别对应无类型的整数、无类型的浮点数、无类型的复数和无类型的字符等不同的常量类型。同样true和false也是无类型的布尔类型字符串面值常量是无类型的字符串类型。

前面说过除法运算符/会根据操作数的类型生成对应类型的结果。因此,不同写法的常量除法表达式可能对应不同的结果:

var f float64 = 212
fmt.Println((f - 32) * 5 / 9)     // "100"; (f - 32) * 5 is a float64
fmt.Println(5 / 9 * (f - 32))     // "0";   5/9 is an untyped integer, 0
fmt.Println(5.0 / 9.0 * (f - 32)) // "100"; 5.0/9.0 is an untyped float

只有常量可以是无类型的。当一个无类型的常量被赋值给一个变量的时候,就像上面的第一行语句,或者是像其余三个语句中右边表达式中含有明确类型的值,无类型的常量将会被隐式转换为对应的类型,如果转换合法的话。

var f float64 = 3 + 0i // untyped complex -> float64
f = 2                  // untyped integer -> float64
f = 1e123              // untyped floating-point -> float64
f = 'a'                // untyped rune -> float64

上面的语句相当于:

var f float64 = float64(3 + 0i)
f = float64(2)
f = float64(1e123)
f = float64('a')

无论是隐式或显式转换,将一种类型转换为另一种类型都要求目标可以表示原始值。对于浮点数和复数,可能会有舍入处理:

const (
	deadbeef = 0xdeadbeef // untyped int with value 3735928559
	a = uint32(deadbeef)  // uint32 with value 3735928559
	b = float32(deadbeef) // float32 with value 3735928576 (rounded up)
	c = float64(deadbeef) // float64 with value 3735928559 (exact)
	d = int32(deadbeef)   // compile error: constant overflows int32
	e = float64(1e309)    // compile error: constant overflows float64
	f = uint(-1)          // compile error: constant underflows uint
)

对于一个没有显式类型的变量声明语法(包括短变量声明语法),无类型的常量会被隐式转为默认的变量类型,就像下面的例子:

i := 0      // untyped integer;        implicit int(0)
r := '\000' // untyped rune;           implicit rune('\000')
f := 0.0    // untyped floating-point; implicit float64(0.0)
c := 0i     // untyped complex;        implicit complex128(0i)

注意默认类型是规则的无类型的整数常量默认转换为int对应不确定的内存大小但是浮点数和复数常量则默认转换为float64和complex128。Go语言本身并没有不确定内存大小的浮点数和复数类型而且如果不知道浮点数类型的话将很难写出正确的数值算法。

如果要给变量一个不同的类型,我们必须显式地将无类型的常量转化为所需的类型,或给声明的变量指定明确的类型,像下面例子这样:

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

当尝试将这些无类型的常量转为一个接口值时见第7章这些默认类型将显得尤为重要因为要靠它们明确接口对应的动态类型。

fmt.Printf("%T\n", 0)      // "int"
fmt.Printf("%T\n", 0.0)    // "float64"
fmt.Printf("%T\n", 0i)     // "complex128"
fmt.Printf("%T\n", '\000') // "int32" (rune)

现在我们已经讲述了Go语言中全部的基础数据类型。下一步将演示如何用基础数据类型组合成数组或结构体等复杂数据类型然后构建用于解决实际编程问题的数据结构这将是第四章的讨论主题。