IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [译]Go语言核心编程 2.5-常量

    abv123456789发表于 2016-03-22 14:01:54
    love 0

    在Go语言中,常量表达式是在编译器求值的,因此在程序运行时是没有性能损耗的。常量的底层类型是前面提过的基本类型:布尔值,字符串,数值变量。

    常量的声明方式和变量很相似,但是常量的值是不可变的,因此在运行期是不可以对常量进行修改的。例如,对于π这种数学常数,常量显然比变量更适合,因为我们不允许这个值发生任何变化:

    const pi = 3.14159 // approximately; math.Pi is a better approximation
    

    可以同时声明多个常量:

    const (
        e  = 2.71828182845904523536028747135266249775724709369995957496696763
        pi = 3.14159265358979323846264338327950288419716939937510582097494459
    )
    

    常量的相关运算也是在编译期完成的,这样不仅可以做相应的编译优化,也可以提升运行时的性能。如果一个表达式的操作数是常量,那么一些运行时的错误就可以提前在编译期发现:整数除以零、字符串索引越界、浮点数计算导致的正负无穷等等。

    常量作为操作数时,以下表达式的结果都是常量:算术、逻辑、比较运算,类型转换,len、cap、real、imag、comlex、unsafe.Sizeof。

    因为常量是在编译器确定的,因此可以作为一些类型的组成部分,比如数组类型的长度:

    const IPv4Len = 4
    
    // parseIPv4函数对IPv4地址(d.d.d.d)进行解析.
    func parseIPv4(s string) IP {
        var p [IPv4Len]byte
        // ...
    }
    

    常量声明时可以指定类型,也可以不指定类型,如果不指定,那么编译器会自己进行类型推断。下面代码中,time.Duration是一个具名类型,底层类型是int64,其中time.Minute是一个time.Duration类型的常量。下面声明的两个常量的类型都是time.Duration,我们可以在fmt中使用%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.Println(a, b, c, d) // "1 1 2 2"
    

    实际场景中,上面的代码并没有太多实用价值。但是我们可以利用它实现下面的常量的iota语法。

    3.6.1. iota

    我们可以使用iota语法来声明一组按照同样规则初始化的常量,优点是不用每行声明都写一遍初始化语句。在一组const声明中,第一个声明的常量的iota值被设置为0,然后接下来每一个行的常量值都会递增1。

    下面这个例子来自time包,首先定义了Weekday具名类型,然后定义了一组常量(一周七天),其中周日的值为0,后面的值依次递增。在C语言中,这种被称为枚举类型(Enum):

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

    周日到周一的值依次是0到6。

    下面是一个更为复杂的例子,来自net包, 它给一个无符号整数的5个低位bit各起了一个名字,用来做基于bit的布尔判断:

    type Flags uint
    
    const (
        FlagUp Flags = 1 << iota // is up
        FlagBroadcast            // supports broadcast access capability
        FlagLoopback             // is a loopback interface
        FlagPointToPoint         // belongs to a point-to-point link
        FlagMulticast            // supports multicast access capability
    )
    

    随着iota的递增,每个常量相应的bit位都会设置为1(位左移),第一个常量为00000001,第二常量为00000010,依次类推。可以使用这些常量用于测试、设置或清除对应bit位的值,也可以用来判断某个值对应的bit是否设置为1(代表着相应的Flag是否设置)。

    func IsUp(v Flags) bool     { return v&FlagUp == FlagUp }
    func TurnDown(v *Flags)     { *v &^= FlagUp }
    func SetBroadcast(v *Flags) { *v |= FlagBroadcast }
    func IsCast(v Flags) bool   { return v&(FlagBroadcast|FlagMulticast) != 0 }
    
    unc main() {
        var v Flags = FlagMulticast | FlagUp
        fmt.Printf("%b %t\n", v, IsUp(v)) // "10001 true"
        TurnDown(&v)
        fmt.Printf("%b %t\n", v, IsUp(v)) // "10000 false"
        SetBroadcast(&v)
        fmt.Printf("%b %t\n", v, IsUp(v))   // "10010 false"
        fmt.Printf("%b %t\n", v, IsCast(v)) // "10010 true"
    }
    

    下面的示例中,每个常量都是1024的幂:

    const (
        _ = 1 << (10 * iota)
        KiB // 1024
        MiB // 1048576
        GiB // 1073741824
        TiB // 1099511627776             (exceeds 1 << 32)
        PiB // 1125899906842624
        EiB // 1152921504606846976
        ZiB // 1180591620717411303424    (exceeds 1 << 64)
        YiB // 1208925819614629174706176
    )
    

    不过iota常量也有其局限性。例如,1000的幂就无法用iota实现,因为Go语言没有幂运算符(只能通过标准库)。

    练习 3.13: 利用尽可能简洁的方式声明KB至YB之间的常量

    3.6.2. 无类型常量


    Go语言中的常量有一点很特别,虽然一个常量可以指定一个特定的基本类型:int或float64或者类似time.Duration这样的具名基础类型,但是实际应用中很多常量声明时都不指定类型。编译器会使用相对基本类型更高的精度来表示这种无类型常量,同时无类型常量的算术运算也会更加精确,你可以假定这种精度至少是256bit。这里有六种无类型常量:无类型布尔值,无类型整数、无类型rune、无类型浮点数、无类型复数以及无类型字符串。
    利用这种无类型常量,不仅可以提供更高的精度,而且在表达式中避免显示的类型转换。例如,上面例子中的ZiB、YiB的值已经超过Go语言中任何整数类型所能表达的范围,但是它们依然是合法的常量,也可以像下面这样使用:

    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类型的,那么最终结果的精度可能不同,同时从浮点数转为复数时需要显示的类型转换:

    const Pi64 float64 = math.Pi
    
    var x float32 = float32(Pi64)
    var y float64 = Pi64
    var z complex128 = complex128(Pi64)
    

    不同的常量值写法会对应不同的类型,虽然0、0.0、0i及'\u0000'有相同的常量值,但是它们分别是:无类型整数、无类型浮点数、无类型复数和无类型rune。同样,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')
    

    无论隐式或者显式转换,将类型A转为类型B都需要B可以表示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。因此如果要给变量一个不同的类型,必须进行显式类型转换:

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

    将无类型常量转为一个接口值时,这种默认类型就很重要,因为这样才能确定接口的动态类型(见第6章)。下面例子中,fmt接收的是接口值inteface{}参数,当把常量直接进行传参时,常量的默认类型就会成为接口值的动态类型。

    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语言中的所有基本类型。下面的章节将学习如果使用基本类型组合成复杂数据类型,然后解决实际编程问题。



沪ICP备19023445号-2号
友情链接