變數宣告、常數宣告


變數(Variable)是儲存值的位置,變數宣告可以給予識別名稱(Identifier)、型態與初始值,在 Go 中寫下的 103.14true"Justin" 等稱之為常數(Constant),常數宣告可給予這些常數識別名稱。

基本變數宣告

要在 Go 中進行變數宣告有多種形式,使用 var 是最基本的方式。例如,宣告一個 x 變數,型態為 int,初始值為 10

var x int = 10

這麼一來,從 x 這個位置開始,儲存了 int 長度的值 10,在宣告變數時,型態是寫在名稱之後。你也可以同時建立多個變數並指定初值:

var x, y, z int = 10, 20, 30

這樣的話,xyz 的型態都是 int,值分別是 102030。如果宣告多個變數時,想要指定不同的型態,可以使用批量宣告:

var (
    x int = 10
    y string = "Justin"
    z bool = true
)

如果宣告變數時指定了型態,但未指定初值,那麼編輯器會提供預設初值,例如:

var (
    a bool        
    b int32
    c float32
    d string
    e complex128
)

在上面的宣告中,abcde 的值分別會是 false00.0""0 + 0i。在 Go 中,宣告了變數,程式中卻沒有取用的動作,那麼會發生 declared and not used 的編譯錯誤。

自動推斷型態

在 Go 中宣告變數並指定值時,可以不用提供型態,由編譯器自動推斷型態,例如:

var x = 10

上頭的 x 型態會是 int,而底下的宣告:

var x, y, z = 10, 3.14, "Justin"

xyz 的型態分別會是 intfloat64string,批量宣告時也可以自動推斷型態,例如:

var (
    x = 10        // int 型態
    y = 3.14      // float32 型態
    z = "Justin"  // string 型態
)

短變數宣告

在函式中,想要定義變數值的場合,可以使用短變數宣告,例如:

x := 10
y := 3.14
z := "Justin"

如果 x 是首次定義,就等於是宣告變數並指定值。上例也可以寫成一行:

x, y, z := 10, 3.14, "Justin"

由於 Go 的函式外,每個語法都必須以關鍵字開始,因此短變數宣告不能在函式外使用。

var 宣告的變數名稱不可重複,然而,短變數宣告時,若同一行內有新宣告了另一變數,就可以重複宣告已存在的變數,例如,以下是合法的,因為使用 := 時有一個新的 y 變數:

var x = 10
x, y :=  20, 30

此時,並沒有建立一個新的 x 變數,只是將新值指定給 x 而已。

由於短變數宣告可以同時宣告變數並指定值,因此對於底下這類需求:

package main

import "fmt"

func main() {
    var x = true
    if x {
        fmt.Println(x)
    }
}

在上例,x 的範圍是整個 main,若改為底下,範圍就只會是 if 區塊:

package main

import "fmt"

func main() {
    if x := true; x {
        fmt.Println(x)
    }
}

類似地,for 之類的語法,也常運用短變數宣告。

(在數學上 A := B 的寫法,涵義是藉由 B 來定義 A,例如數學上若已經定義 x 以及 f(x)x := f(x) 表示用舊的 x 定義新的 x,這反而像是程式語言中的 x = f(x) 指定的概念,當然,數學上的符號與程式語言中的符號是有出入的,Go 在這邊只是借用了 := 來作為另一種變數宣告符號。)

調換變數值

在 Go 中,要調換兩變數的值很簡單,例如底下的程式執行過後,x 會是 20,而 y 會是 10

var x = 10
var y = 20
x, y = y, x

基本常數宣告

如一開始談到的,在 Go 中寫下的 103.14true"Justin" 等稱之為常數(Constant),常數宣告可給予這些常數識別名稱,要給予名稱時使用的是 const 關鍵字,例如:

const x = 10

正如〈認識預定義型態〉中談過的,10 會是一個整數常數,不過型態未定,如果要定義一個常數的型態,可以使用 int32()int64() 之類的函式進行型態轉換,或者是在使用 const 宣告常數名稱時指定型態,例如:

const x int32 = 10

這邊的 10 就是 int32 型態了,注意,這邊的 x 並不是一個變數,而是一個識別名稱罷了,因此,會說 x 常數的型態是 int32,而不能說 x 變數的型態是 int32

如果有多個常數要宣告,也可以批量宣告,例如:

const (
    x = 10
    y = 3.14
    z = "Justin"
)

再次地,xyz 分別是未定型態的整數、浮點數與字串常數(而不是 intfloat64string 這三個 Go 中定義的型態),如果你想要讓他們為已定義型態的整數、浮點數與字串常數,可以在宣告時指定型態:

const (
    x int = 10
    y float32 = 3.14
    z string = "Justin"
)

由於常數並非變數,因此,宣告了常數並不一定要用到,底下的程式不會發生錯誤:

package main

import "fmt"

func main() {
    const (
        x = 10
        y = 3.14
        z = "Justin"
    )

    fmt.Println(x)
    fmt.Println(y)
}

常數運算式

由於常數可以是未定型態,因此一個有趣的地方就是,像 2 + 3.015 / 415 / 4.0 這樣的常數運算式,該怎麼在編譯時期決定它們的值?答案是根據運算式中的常數運算元是整數、rune(單引號括住的常數)、浮點數或複數來決定,如果運算式中包括了越後面的常數,就會用它來決定。

因此,2 + 3.0 會是未定型態的浮點數 5.015 / 4 會是未定型態的整數 3,然而,15 / 4.0,會是浮點數型態的 3.75,在規格書的〈Constant expressions〉中,列出了說明以及一些範例,例如:

const a = 2 + 3.0          // a == 5.0   (untyped floating-point constant)
const b = 15 / 4           // b == 3     (untyped integer constant)
const c = 15 / 4.0         // c == 3.75  (untyped floating-point constant)
const Θ float64 = 3/2      // Θ == 1.0   (type float64, 3/2 is integer division)
const Π float64 = 3/2.     // Π == 1.5   (type float64, 3/2. is float division)
const d = 1 << 3.0         // d == 8     (untyped integer constant)
const e = 1.0 << 3         // e == 8     (untyped integer constant)
const f = int32(1) << 33   // illegal    (constant 8589934592 overflows int32)
const g = float64(2) >> 1  // illegal    (float64(2) is a typed floating-point constant)
const h = "foo" > "bar"    // h == true  (untyped boolean constant)
const j = true             // j == true  (untyped boolean constant)
const k = 'w' + 1          // k == 'x'   (untyped rune constant)
const l = "hi"             // l == "hi"  (untyped string constant)
const m = string(k)        // m == "x"   (type string)
const Σ = 1 - 0.707i       //            (untyped complex constant)
const Δ = Σ + 2.0e-4       //            (untyped complex constant)
const Φ = iota*1i - 1/1i   //            (untyped complex constant)

現在,應該能明白,〈認識預定義型態〉中 math.MaxInt64 若不加上 int64,何以會 overflow 的錯誤了。

附帶一提的是,在 Go 中,模組中定義的名稱若要能在模組外可見,必須是首字大寫,而對於像 math.MaxInt64 這類的公用常數,可以定義在一個 .go 檔案之中,例如 math.MaxInt64,就是定義在一個 const.go 之中。

使用 iota 列舉

如果要需要列舉一些常數時,可以使用 iota,它每遇到一次 const,就會重置為 0,若它在批量常數宣告中使用時,第一次出現時的預設值是 0,每出現一次就遞增 1,例如:

const (
    x = iota   // 0
    y = iota   // 1
    z = iota   // 2
 )

因為 const 批量宣告時,若後面的值沒寫出,會使用前一個值設定,例如:

const (
    x = 1
    y      // 1
    z      // 1
 )

因此,如果是連續的列舉,只要寫一次 iota 就可以了,這表示後續的值,也都使用 iota,結果就是:

const (
    x = iota   // 0
    y          // 1
    z          // 2
 )

其實也可以這麼寫來列舉常數,只是比較麻煩:

const x, y, z = iota, iota, iota