有時候你可能需要將變數轉換為其他類型。Golang 不容許隨意處理這種轉換,轉換是由類型系統的強制保證的某些規則。在這篇文章中,我們將討論哪些轉換是可能的,哪些是不可能,以及什麼時候進行轉換是有價值的。Go 是一門強型別語言。它在類型上是嚴格的,編譯期間會報告類型錯誤。```gopackage mainimport "fmt"func main() { monster := 1 + "2" fmt.Printf("monster: %v\n", monster)}``````> go build# github.com/mlowicki/lab./lab.go:6: cannot convert "2" to type int./lab.go:6: invalid operation: 1 + "2" (mismatched types int and string)```JavaScript 是弱類型語言的一種,讓我們看看它的實際效果:```javascriptvar monster = 1 + "foo" + function() {};console.info("type:", typeof monster)console.info("value:", monster);```我將數字,字串甚至函數加在一起,這似乎是件奇怪的事情。但不用擔心,JavaScript 會無報錯地為你處理這些事情。```type: stringvalue: 1foofunction () {}```在特定的情況,可能需要將一個變數轉為其他類型,例如將它作為函數參數傳遞,或是放進某個運算式中。```gofunc f(text string) { fmt.Println(text)}func main() { f(string(65)) // 整型常量轉換為字串。}```函數調用的運算式是類型轉換的常見情況, 下面我們會多次看到類似的轉換。上面的代碼是能夠正常啟動並執行,但是如果移除類型轉換:```gof(65)```會導致一個編譯時間錯誤:"cannot use 65 (type int) as type string in argument to f" (無法將整數類型的 65 作為字串型別參數給 f )## 底層類型(Underlying type)字串,布爾值,數字或者字面量類型的底層類型仍是他們本身,其他情況下,型別宣告定義了底層類型:```gotype A string // stringtype B A // stringtype C map[string]float64 // map[string]float64 (type literal)type D C // map[string]float64type E *D // *D```(注釋裡是對應的底層類型)如果底層類型是相同的,那麼類型轉換時百分百有效。```gopackage maintype A stringtype B Atype C map[string]float64type C2 map[string]float64type D Cfunc main() { var a A = "a" var b B = "b" c := make(C) c2 := make(C2) d := make(D) a = A(b) b = B(a) c = C(c2) c = C(d) var _ map[string]float64 = map[string]float64(c)}```上面的程式不會有任何的編譯問題。**底層類型的定義不能是遞迴的( Definition of underlying types isn’t recursive)****譯按**:> 關於底層類型的定義不能是遞迴的情況,譯者對這種說法保持懷疑。> 底層類型的定義要麼解釋到內建類型(int, int64, float, string, bool...), 要麼遞迴解釋到 unnamed type 的。例如> * B->A->string, string 為內建類型,解釋停止, B 的底層類型為 string;> * U->T->map[S]float64, map[S]float64 為 unnamed type, 解釋停止,U 的底層類型為 map[S]float64。> ```> type A string // string> type B A // string> type S string // string> type T map[S]float64 // map[S]float64> type U T // map[S]float64> ``````gotype S stringtype T map[S]float64....var _ map[string]float64 = make(T)```上面的程式會在編譯期間報錯:```cannot use make(T) (type T) as type map[string]float64 in assignment```賦值錯誤的發生,是因為 *T* 的底層類型不是 `map[string]float64` 而是 `map[S]float64`。類型轉換也會報錯:```govar _ map[string]float64 = (map[string]float64)(make(T))```上面的代碼會在編譯時間導致如下錯誤:```cannot convert make(T) (type T) to type map[string]float64```## 可賦值性(Assignability)Go 語言規範提出*可賦值性(Assignability)*的概念,它定義了什麼時候一個變數 *v* 能夠被賦值給 *T* 類型的變數。讓我們在代碼中瞭解它的一個規則:賦值時,兩者應該具有相同的底層類型,並且至少有一個不是 named 類型。```gopackage mainimport "fmt"func f(n [2]int) { fmt.Println(n)}type T [2]intfunc main() { var v T f(v)}```程式輸出 “[0 0]”。在可賦值性規則許可的範圍內,所有的類型轉換都是可行的。程式員能用這種方式清晰地表達他的具體的想法:```gof([2]int(v))```上面的調用方法會和之前的得到一樣的結果。關於可賦值性更多資訊可以在[之前的文章](https://studygolang.com/articles/12381)中找到。> 類型轉換的第一個規則(具有相同的底層類型)和可賦值規則的一條規則是重合的 - 當底層類型是相同的時候,至少有一個的類型是 unnmaed 類型(這一節的第一個例子)。較弱的規則會影響更嚴格的規則。因此當類型轉換時,只需要底層類型保持一致,是否是 named/unnamed 類型並不重要。## 常量常量 *v* 能夠被轉換為類型 *T*, 當 *v* 能被 *T* 類型的變數表示時。```goa := uint32(1<<32 – 1)//b := uint32(1 << 32) // constant 4294967296 overflows uint32c := float32(3.4e38)//d := float32(3.4e39) // constant 3.4e+39 overflows float32e := string("foo")//f := uint32(1.1) // constant 1.1 truncated to integerg := bool(true)//h := bool(1) // convert 1 (type int) to type booli := rune('ł')j := complex128(0.0 + 1.0i)k := string(65)```對於常量更深入的介紹可以在[官方部落格](https://blog.golang.org/constants)中找到。## 數字類型### 浮點數(floating-point number) -> 整數(integer)```govar n float64 = 1.1var m int64 = int64(n)fmt.Println(m)```小數部分被移除,因此代碼輸出 ”1“。對於其他轉換:* 浮點數 -> 浮點數,* 整數 -> 整數,* 整數 -> 浮點數,* 複數 -> 複數。變數會被四捨五入至目標精度:```govar a int64 = 2 << 60var b int32 = int32(a)fmt.Println(a) // 2305843009213693952fmt.Println(b) // 0a = 2 << 30b = int32(a)fmt.Println(a) // 2147483648fmt.Println(b) // -2147483648b = 2 << 10a = int64(b)fmt.Println(a) // 2048fmt.Println(b) // 2048```## 指標*可賦值性(Assignability)* 以和處理其他類型一樣的方式處理指標類型。```gopackage mainimport "fmt"type T *int64func main() { var n int64 = 1 var m int64 = 2 var p T = &n var q *int64 = &m p = q fmt.Println(*p)}```程式正常工作並且輸出 ”2“,這依賴於已經被討論過的*可賦值性規則(assignability rule)*。**int64* 和 *T* 的底層類型是相同的,並且 **int64* 是 unnamed 類型。類型轉換則更寬鬆一些。對於 unnamed 指標類型,指標的基底類型(base type)具有相同的底層類型即可轉換。> 譯按: 指標的基底類型(base type)為指標所指向變數的類型,例如 p *int, p 的基底類型為 int。```gopackage mainimport "fmt"type T int64type U Wtype W int64func main() { var n T = 1 var m U = 2 var p *T = &n var q *U = &m p = (*T)(q) fmt.Println(*p)}```> **T* 應該在括弧內,否則他會被理解成 *(T(q))和之前的程式一樣,輸出 “2”。因為 *U* 和 *T* 的在作為**U* 和 **T* 的基底類型的同時,他們的底層類型是相同的。 如下的賦值操作:```gop = q```是不會成功的,因為它嘗試處理兩種不同的底層類型: **T* 和 **U*。作為練習,讓我們稍微改變聲明,看會發生什麼```gotype T *int64type U *Wtype W int64func main() { var n int64 = 1 var m W = 2 var p T = &n var q U = &m p = T(q) fmt.Println(*p)}```*U* 和 *W* 的聲明已經被改變。思考一下,會發生什麼?編譯器在以下位置報一個錯誤 “cannot convert q (type U) to type T”(無法將 q (類型 U) 轉換為類型 T):```gop = T(q)```這是因為 *p* 的底層類型是 **int64*,而 *q* 的是 **W*。q 的類型是 named(*U*),因此擷取指標基底類型的底層類型的規則並不適用在這裡。## 字串### 整數 -> 字串傳遞數字 *N* 到 *string* 內建地將 N 轉化為 UTF-8 編碼的字串,該字串是 N 表達的字元組成。```gofmt.Printf("%s\n", string(65))fmt.Printf("%s\n", string(322))fmt.Printf("%s\n", string(123456))fmt.Printf("%s\n", string(-1))```輸出:```Ał��```前兩個轉換使用完全有效碼位。也許你會好奇為什麼後兩行顯示奇怪的符號。它是一個*替換字元(replacement character)*,是稱為 *specials* Unicode 區段的一員。它的編碼為 \uFFFD ( [更多資訊](https://en.wikipedia.org/wiki/Specials_%28Unicode_block%29))### 對 strings 的簡要介紹Strings 基本上是位元組的切片:```gotext := "abł"for i := 0; i < len(text); i++ { fmt.Println(text[i])}```輸出:```9798197130```97 和 98 是 UTF-8 編碼的 “a” 和 “b“ 字元。第三和第四行的輸出是字元 “ł” 的 UTF8 編碼,該編碼佔據了兩個位元組的空間。*range* 迴圈有助於迭代 Unicode 定義的碼位( 碼位在 Golang 中被稱為 *rune* )```gotext := "abł"for _, s := range text { fmt.Printf("%q %#v\n", s, s)}```輸出:```'a' 97'b' 98'ł' 322```> 想瞭解更多類似 *%q* 和 *%v* 這樣的預留位置,可以看 [fmt](https://golang.org/pkg/fmt/) 包的文檔更多的討論可在 [《Golang的字串,位元組,rune 和字元》](https://blog.golang.org/strings)。在這個快速解釋之後,在字串和位元組切片之間的轉換應該不會再難以理解。### string slice of bytes```gobytes := []byte("abł")text := string(bytes)fmt.Printf("%#v\n", bytes) // []byte{0x61, 0x62, 0xc5, 0x82}fmt.Printf("%#v\n", text) // "abł"```切片由被轉換 string 的 utf8 編碼位元組組成。### string slice of runes```gorunes := []rune("abł")fmt.Printf("%#v\n", runes) // []int32{97, 98, 322}fmt.Printf("%+q\n", runes) // ['a' 'b' '\u0142']fmt.Printf("%#v\n", string(runes)) // "abł"```從被轉換 string 中建立的切片是由 Unicode 編碼的碼位( rune )組成。## 結尾如果你喜歡這篇文章,請關注[我](https://medium.com/golangspec),以便你收到最新的文章推送訊息。## 資料* [Golang 語言規範](https://golang.org/ref/spec#Conversions)* [Go 的 Strings, bytes, runes 和字元](https://blog.golang.org/strings)* [Go 的賦值性](https://studygolang.com/articles/12381)* [Go 的常量](https://blog.golang.org/constants)
via: https://medium.com/golangspec/conversions-in-go-4301e8d84067
作者:Michał Łowicki 譯者:magichan 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
211 次點擊