認識預定義型態


Go 包括了一些預先定義型態(Pre-declared Type),這包括了布林、數字與字串型態。

布林型態

預定義型態也是具有名稱的型態(Named Type),布林型態名稱為 bool,只有兩個預先定義的常數 truefalse,由於只有兩個值,因此在 Go 的規格書 中,並沒有明確提及 bool 的大小,雖然在 Go 官方網站的 The Go Playground 執行以下程式碼,會告訴你 bool 大小是 1:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    fmt.Println(unsafe.Sizeof(true))
    fmt.Println(unsafe.Sizeof(false))
}

附帶一提的是,Go 本身沒有提供 REPL 工具,不過 Go 官方網站的 The Go Playground 是個方便的介面,你也可以在 Does Go provide REPL? 找到一些其他開發者寫的 REPL。

數字型態

數字型態為整數與浮點數的集合,整數部份支援無號與有號整數,名稱分別為 uintintint 長度會與 uint 相同,而 uint 長度視平台實作而異,可能是 32 位元或是 64 位元。

如果想要長度固定,無號整數的型態名稱為 uint8uint16uint32uint64,顧名思義,使用的長度分別為 8 位元、16 位元、32 位元與 64 位元,舉例來說,uint8 可儲存的整數範圍為 0 到 255,這也是開發者熟悉的位元組型態,而在 Go 中,byte 正是 uint8 的別名。

有號整數的型態名稱為 int8int16int32int64,顧名思義,使用的長度分別為 8 位元、16 位元、32 位元與 64 位元,舉例來說,int32 可儲存的整數範圍為 -2147483648 到 2147483647,而 runeint32 的別名,可用來儲存 Unicode 碼點(code point)。

如果直接寫下一個整數實字(literal),例如 10,在沒有程式上下文(context)的情況下,10 是未定型態(Untyped),未定義型態整數的預設型態(Default type)為 int 型態,在必須得到一個型態而程式上下文未提供時(例如變數宣告與賦值要進行型態推斷時),就會使用預設型態。

寫下 10 這樣的整數,預設是 10 進位制;可以在數字前加上 0,Go 1.13 後可使用 0o 來表示八進位制,加上 0x 表示 16 進位制,此時 a-f and A-F 都可以用來表示 10 到 15,例如 0xBadFace,G○ 1.13 後可以使用 0x1.0p-1021 來表示浮點數。

Go 1.13 後,可以使用 0b 來定義二進位數字,例如 0b00101101;數字分隔底線在 Go 1.13 後可以使用,例如 1_000_0000b_1010_01103.1415_9265

math 模組上定義了一些常數,可以讓你得知各整數型態的儲存範圍,例如以下程式顯示了各整數型態的儲存範圍:

package main

import (
    "fmt"
    "math"
    "reflect"
)

func main() {
    fmt.Printf("uint8  : 0 ~ %d\n", math.MaxUint8)
    fmt.Printf("uint16 : 0 ~ %d\n", math.MaxUint16)
    fmt.Printf("uint32 : 0 ~ %d\n", math.MaxUint32)
    fmt.Printf("uint64 : 0 ~ %d\n", uint64(math.MaxUint64))
    fmt.Printf("int8   : %d ~ %d\n", math.MinInt8, math.MaxInt8)
    fmt.Printf("int16  : %d ~ %d\n", math.MinInt16, math.MaxInt16)
    fmt.Printf("int32  : %d ~ %d\n", math.MinInt32, math.MaxInt32)
    fmt.Printf("int64  : %d ~ %d\n", math.MinInt64, math.MaxInt64)
    fmt.Printf("整數預設型態: %s\n", reflect.TypeOf(1))
}

執行結果如下:

uint8  : 0 ~ 255
uint16 : 0 ~ 65535
uint32 : 0 ~ 4294967295
uint64 : 0 ~ 18446744073709551615
int8   : -128 ~ 127
int16  : -32768 ~ 32767
int32  : -2147483648 ~ 2147483647
int64  : -9223372036854775808 ~ 9223372036854775807
整數預設型態: int

注意到,程式中使用了 uint64 函式,對 math 的一些常數做了明確的型態轉換(Type conversion),這是因為在 Go 中,常數可以是未定型態(Untyped),實際型態會視當時程式環境而定,如果沒有可參考的環境資訊,會使用預設型態。

在這邊的例子中,若是拿掉 uint64math.MaxUint64 就會採用 int 型態而發生 overflow 的錯誤,使用 uint64 函式進行型態轉換,讓常數有明確的環境資訊可以參考,就不會產生這個錯誤。

在 Go 中,不同型態之間也無法直接進行運算,就算都是整數也不行,例如以下會發生 mismatched types 錯誤:

package main

import "fmt"

func main() {
    var x int32 = 10
    var y int16 = 20
    fmt.Println(x + y) // mismatched types error
}

想要避免錯誤,你必須明確進行型態轉換,例如寫為 x + int32(y)(或者是 int16(x) + y)。那麼,下面這個會不會發生錯誤呢?

package main

import "fmt"

func main() {
    var x int8 = 10
    fmt.Println(x + 20)
}

不會!結果會顯示 30,為什麼?正如先前談過,寫下 20 這個整數,它是未定型態,根據 x + 20,進行 int8 的運算,所以不會發生錯誤,不這樣的話,每次都得寫 x + int8(20),實在就夠煩的了!

在 Go 中並沒有字元對應的型態,只有碼點的概念,int32 或其別名 rune 可用來儲存 Unicode 碼點,你可以將單一文字包在單引號之中,例如 '林',這會以 int32 儲存為 26519,例如 fmt.Println('林') 會顯示 26519,若想顯示為文字,則要使用 fmt.Println(string('林'))

浮點數的名稱為 float32float64,分別為 IEEE-754 32 位元與 64 位元浮點數,如果直接寫下一個浮點數實字,預設型態是 float64 型態,可使用科學記號,例如 1.e+06.67428e-11 等,常數 math.MaxFloat32math.MaxFloat64 分別代表著浮點數的最大儲存範圍。

Go 還有複數(Complex number),其中 complex64complex128,可由一個實部數字,加上一個虛部數字與 i 來表示複數,例如 1 + 2i,寫下一個複數實字,預設型態為 complex128,虛數的部份,在 Go 1.13 後,之前談到的數字表示法都可以使用,有三個函式可以用來處理複數,即 complexrealimag,可參考〈Manipulating complex numbers〉。

Go 還有個 uintptr,可以用來儲存指標值,這之後有機會再來談。

字串型態

Go 的字串在實作上使用 UTF-8,就目前必須先知道的是,當使用雙引號包裹一系列文字,會產生字串型態,預設型態為 string,例如,"Justin" 會建立一個字串。

如果對字串使用 len 函式,傳回的會是位元組數量,而不是 Unicode 碼點的數量;如果使用 [] 搭配索引,取得特定索引位置的值,那麼傳回的會是 byteuint8)型態。

在 Go 中,可以對字串使用切片(slice)操作,傳回的型態會是 string 型態,例如,"Justin"[0:2] 會傳回字串 "Ju",不過,這是取得索引 0、1 處的位元組,再建立 string 傳回,因此,對於 "語言" 這個字串,如果想用切片操作取得 "語" 這個子字串,必須使用 "語言"[0:3],因為 Go 的字串在實作上使用 UTF-8,一個中文字基本上佔三個位元組。

Go 的字串還有許多值得說明的細節,這之後會再做詳細討論。