034-常量

无论你是学习 C/C++ 还是 Java,肯定遇到过常量这种概念。Go 语言当然也有常量,在前面的文章里,你肯定遇见过很多次了。下面就是在 Go 里定义的一个常量。

const pi = 3.1415926

不过很奇怪的是,为什么定义常量的时候没有指定类型?它也是自动推导的吗?这个问题待会我们再说清楚。

1. 一些特性

  • Go 语言的常量是在编译期进行计算的
  • 常量之间所有的运算,结果仍然是常量。这个特性可以提升程序的运行效率,将运行期的计算转移到编译期完成,是一种常见的编程技法。

举个例子:

const a = 2
const b = 2 * a // b 在编译期完成计算

2. const 关键字

正如上述所见,Go 中使用 const 关键字声明常量。

  • 例 1
package main

import "fmt"

const (
    pi = 3.141592653589793238462643383279
    e  = 2.718281828459045235360287471352
    i  = 100
    ii = 2 * i
    c  = 4 + 0i
    r  = 'a'
    rr = '中'
    s  = "中国"
    b  = true
)

func main() {
    fmt.Printf("%T: %[1]v\n", pi) // float64: 3.141592653589793
    fmt.Printf("%T: %[1]v\n", e)  // float64: 2.718281828459045
    fmt.Printf("%T: %[1]v\n", i)  // int: 100
    fmt.Printf("%T: %[1]v\n", ii) // int: w00
    fmt.Printf("%T: %[1]v\n", c)  // complex128: (4+0i)
    fmt.Printf("%T: %[1]v\n", r)  // int32: 97
    fmt.Printf("%T: %[1]v\n", rr) // int32: 20013
    fmt.Printf("%T: %[1]v\n", s)  // string: 中国
    fmt.Printf("%T: %[1]v\n", b)  // bool: true

    var x float64 = i
    fmt.Printf("%T: %[1]v\n", x)  // float64: 100
    fmt.Printf("%T: %[1]v\n", i/6)// int: 16
}
  • 例 2
package main

import "fmt"

func main() {
    const (
        a         = 8
        b         = 2.2 * a
        c float64 = 5
    )
    fmt.Printf("%T %[1]v\n", a) // int 8
    fmt.Printf("%T %[1]v\n", b) // float64 17.6
    fmt.Printf("%T %[1]v\n", c) // float64 5
}

上面的两个例子里,大多数 const 声明的常量都是没有指定类型的,但是例 2 中,你也可以显式指定类型。没有指定类型的常量,在 Go 里称之为无类型常量(untyped constant),但并不是真的就是没有类型。接下来,我们来说说这种 untyped constant 是啥。

2.1 untyped constant

实际上,没有显式指定 type 的常量,在 Go 里称为 uncommitted constant (未提交、未明确的常量),意思就是说这种常量的类型暂时还不能确认,以后在用到它的时候再确认也不迟。

换言之,那些显式指定了类型的 constant 称为 committed constant.

举例:

const a = 10
fmt.Printf("%T\n", a)
var b float64 = 4 * a // 在需要的时候,a 转变成了 float64
fmt.Printf("%T\n", b)

在 Go 里,uncommitted constant 拥有比普通的 int/float64 更高的运算精度(gopl 说可以认为其精度达到 256bit)。

前面我们也说了,untyped constant 并不是说它没有类型,实际上在第 1 节的两个例子中,我们看到了 untyped constant 可以通过 Printf 函数打印出其类型,这是因为在 constant 在此处隐式转换成了对应基础类型的常量。

这些隐式转换的规则是什么?

Go 里,有 6 种 uncommitted constant,分别是:

  • untyped boolean
  • untyped integer (隐式转换成 int)
  • untyped rune (隐式转换成 int32)
  • untyped floaing-point (隐式转换成 float64)
  • untyped complex (隐匿转换成 complex128)
  • untyped string

uncommitted constant 另一个特点是在做类型转换时,不需要显式的进行强制转换,前面也说了,它会在需要的时候进行类型转换。

3. iota

这里写图片描述

在 Go 里,iota 被称之为 constant generator (常量生成器)。你可以把 iota 理解成一个函数(并不真的是函数,它是由编译器自动处理的),这个函数每使用一次,返回的值就自动 + 1.

  • 例 1
package main

import "fmt"

const (
    a = iota
    b = iota + 9
    c = iota - 4
    d = iota
)

func main() {
    fmt.Printf("%T: %[1]v\n", a) // int: 0
    fmt.Printf("%T: %[1]v\n", b) // int: 10
    fmt.Printf("%T: %[1]v\n", c) // int: -2
    fmt.Printf("%T: %[1]v\n", d) // int: 3
}

不过在实际使用中,如果后面一行的 iota 表达式和上一行完全一样,则可以省略不写,见例 2.

  • 例 2
package main

import "fmt"

const (
    Sunday = iota
    Monday // = iota
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

func main() {
    fmt.Printf("%T: %[1]v\n", Sunday)     // int: 0
    fmt.Printf("%T: %[1]v\n", Monday)     // int: 1
    fmt.Printf("%T: %[1]v\n", Tuesday)    // int: 2
    fmt.Printf("%T: %[1]v\n", Wednesday)  // int: 3
    fmt.Printf("%T: %[1]v\n", Thursday)   // int: 4
    fmt.Printf("%T: %[1]v\n", Friday)     // int: 5
    fmt.Printf("%T: %[1]v\n", Saturday)   // int: 6
}

这特别像 C++ 语言中的枚举类型,给第一个元素赋值后,后面的元素值自动 + 1. 不过 iota 更加强大,它可以生成更加复杂的值:

  • 例3
package main

import "fmt"

const (
    a = 1 << iota
    b // = 1 << iota,后面都不用写了,都一样
    c
    d
)

func main() {
    fmt.Printf("%T: %[1]v\n", a)  // int: 1
    fmt.Printf("%T: %[1]v\n", b)  // int: 2
    fmt.Printf("%T: %[1]v\n", c)  // int: 4
    fmt.Printf("%T: %[1]v\n", d)  // int: 8
}
  • 例 4
package main

import "fmt"

const (
    _ = 1 << (10 * iota)
    KiB
    MiB
    GiB
    TiB
    PiB
    EiB
    ZiB
    YiB
)

func main() {
    fmt.Printf("%T: %[1]v\n", KiB)     // int: 1024
    fmt.Printf("%T: %[1]v\n", MiB)     // int: 1048576
    fmt.Printf("%T: %[1]v\n", GiB)     // int: 1073741824
    fmt.Printf("%T: %[1]v\n", TiB)     // int: 1099511627776
    fmt.Printf("%T: %[1]v\n", PiB)     // int: 1125899906842624
    fmt.Printf("%T: %[1]v\n", EiB)     // int: 1152921504606846976
    // 还记得吗?常量之间的运算结果仍然是常量。YiB/ZiB 是编译期计算出来的。
    fmt.Printf("%T: %[1]v\n", YiB/ZiB) // int: 1024
    // 下面这两行不注释掉,编译会报错。
    // fmt.Printf("%T: %[1]v\n", ZiB)
    // fmt.Printf("%T: %[1]v\n", YiB)
}

4. 总结

  • 掌握 const 关键字
  • 掌握 iota
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值