昨天將結構 (struct) 完整的介紹完畢,今天就來介紹如何檢查型別。
在本次的鐵人賽曾多次提到 Go 語言是強型別的語言,也就是遇到函式引數型別和實際叫用型別不符合的情況,是會直接出錯或者編譯失敗。
但是有時候就會遇到型別不一致的時候,這時候可以選擇使用 型別轉換(type conversion) ,將其中一個的值轉成跟另一個值相同的型別:
<型別>(<值>)
但是在做型別轉換時需要特別注意,你轉換的型別會不會可能有問題,例如:若想要將 int64 變數轉成 int8 ,就要特別注意有沒有可能發生溢位的錯誤,因為 int64 的儲存範圍比 int8 大,所以稍不注意就可能發生溢位的錯誤。
以及若是今天想將 float 轉成 int 時,原本 float 後面的小數點便會被無條件捨去。再進行型別轉換時,可能會損失資料(如損失小數點後的位數),但不用擔心這都是合理的行為,且在真實世界也是很常見的 (畢竟也不可能將 float 轉成 int ,然後又威脅人家不能省略小數點後的位數,那乾脆不要轉換?!) ,所以其實只需特別注意溢位問題,就可以安心做轉換啦!
雖然型別轉換是方便的,但也總有不能互相轉換的型別們,正常來說你不能強迫將一個字串型別轉換成數字型別 ; 布林值也不能轉換成數字或是字串型別。但 Go 語言還是有提供 strconv 套件,是個可以將字串轉換成其他型別的方便小工具。
相信之前定義常數變數時,有說過若在定義時省略型別, Go 語言是會自動根據給予的初始值,去推斷型別的,基於這個特性,我們可以先定義一個沒有指定型別的數值常數,然後後續再將他賦值給特定數值型別的變數。
x := true // 會自動推測為 bool 型別
y := 10 // 會自動推測為 int 型別
z := "Go" // 會自動推測為 string 型別
範例 1:
package main
import "fmt"
func change() string{
var maxInt8 int8 = 127
greaterThanMaxInt8 := 128
float := 9.999
newInt := fmt.Sprintf("maxInt8: %v ,int64: %v\n",maxInt8,int64(maxInt8)) // 將 int8 轉換成型別 int64,因為int64 型別的容量較大,所以可以正常轉換
newInt += fmt.Sprintf("greaterThanMaxInt8: %v ,int8: %v\n",greaterThanMaxInt8,int8(greaterThanMaxInt8)) // 因為 greaterThanMaxInt8 大於 int8 的最大值,所以會發生越界繞回的問題
newInt += fmt.Sprintf("maxInt8: %v ,float64: %v\n",maxInt8,float64(maxInt8))
newInt += fmt.Sprintf("float: %v ,int: %v\n",float,int(float)) // 將 float 轉換為 int 小數點會直接捨去
return newInt
}
func main() {
fmt.Println(change())
}
範例 1(執行結果):
maxInt8: 127 ,int64: 127
greaterThanMaxInt8: 128 ,int8: -128
maxInt8: 127 ,float64: 127
float: 9.999 ,int: 9
從開賽至今也已經 20 幾天了,相信大家應該有發現我們經常使用 fmt.Print()
函式來將資料印出,但不知道大家有沒有想過, Go 是強型別語言,若是型別不同是會報錯的,但為什麼我們使用 fmt.Print()
卻不會遇到型別不同的錯誤呢?
根據官方文件的說明,fmt.Print() 內可以接受 interface{} 型別的參數。
func Printf(format string, a ...interface{}) (n int, err error)
至於 介面(interface) 詳細的話會在後面章節詳細說明,我們可以先簡單理解為 介面(interface) 是一個規範,會列出很多個定義的函式(方法),只要任何型別有具備相同的函式(方法),就是是為符合介面的型別,然後去實踐 interface 裡面的方法。我將它想像成介面就像是一個驗票員,只要你有符合的票,那介面就會讓你執行方法。
而 interface{} 的型別就是個沒有指定任何函數的空型別,它自己也不具備任何欄位。在 Go 語言中無論是內建或是自訂型別,都會符合 interface{} 的型別,這個正是我們前面提出:「為什麼我們使用 fmt.Print()
卻不會遇到型別不同的錯誤」疑問的解答。
當今天想要存取傳入 interface{} 變數的方法時,是沒有辦法的,因為上面有說介面是空型別,裡面也沒有任何方法(但另一方面也是因為如此,空見面才能維持型別的安全性),但若想要挖出被 interface{} 埋沒的資料型別功能,我們就要使用 型別斷言(type assertion) 來將值轉成指定型別並回傳。
型別斷言會將值轉成指定型別並回傳,使用方法如下:
<值> := <變數名稱>.(<型別>)
也可以接收第二個參數,用來回傳轉換是否成功。若不接收第二個參數的回傳值,當轉換失敗時, Go 語言會引發 panic。
<值>, <ok> := <變數名稱>.(<型別>)
簡而言之,當今天想要將值傳入 interface{} 裡,原本值的內容不會消失,但你卻看不見也摸不著,只有使用型別斷言 (type assertion) 才有辦法取值。而在執行型別斷言時, Go 語言編譯時一樣會做型別的檢查,若是檢查發現失效,那就會噴錯,這時處理錯誤訊息就非常重要了,而處理錯誤訊息一樣後面章節會介紹到,這邊就不詳細說明。
範例 2:
package main
import (
"errors"
"fmt"
)
// 接收一個 interface{} 型別的函式
func doubler(value interface{}) (string, error) {
if int, ok := value.(int); ok {
return fmt.Sprint(int * 2), nil
}
if str, ok := value.(string); ok {
return str + str, nil
}
// 型別不符前面的檢查, 傳回錯誤
return "", errors.New("輸入的值錯誤")
}
func main() {
res, _ := doubler(5)
fmt.Println("5 :", res)
res, _ = doubler("good")
fmt.Println("good :", res)
_, err := doubler(true)
fmt.Println("true :", err)
}
範例 2(執行結果):
5 : 10
good : goodgood
true : 輸入的值錯誤
switch <值> := <變數>.(型別) {
case <型別>:
<敘述>
case <型別>, <型別>:
<敘述>
default:
<敘述>
}
型別 switch 會執行符合 case 型別的敘述,且把值設定成指定的型別,但當你在 case 後比對不只一種型別,那他就會不知道要把值指定成哪個型別,所以就還是得在 case 下的敘述加上型別斷言。
範例 3:
package main
import (
"errors"
"fmt"
)
func doubler(value interface{}) (string, error) {
switch t := value.(type){
case string:
return t + t, nil
case bool:
if t {
return "truetrue", nil
}
return "falsefalse", nil
case float32, float64:
if f, ok := t.(float64); ok {
return fmt.Sprint(f * 2), nil
}
return fmt.Sprint(t.(float32)*2), nil
case int:
return fmt.Sprint(t * 2),nil
default:
return "", errors.New("輸入的值錯誤")
}
}
func main() {
res, _ := doubler(-5)
fmt.Println("-5 :", res)
res, _ = doubler(5)
fmt.Println("5 :", res)
res, _ = doubler("good")
fmt.Println("good :", res)
res, _ = doubler(true)
fmt.Println("true :", res)
res, _ = doubler(float32(9.99))
fmt.Println("9.99 :", res)
}
範例 3(執行結果):
-5 : -10
5 : 10
good : goodgood
true : truetrue
9.99 : 19.98
如果我們不需要精確控制型別斷言檢查,利用型別 switch 敘述就能簡化安全型別,但同時可以讓我們掌控型別斷言檢查的結果。
明天要來介紹,如何將程式碼集中成為可以重複使用的元件,也就是函式(function),那我們明天見~