天天看點

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

034-常量

在 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      
  • 例 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      
  • 例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