【Golang】關於Go中的類型轉換


在使用 go 這樣的強類型語言時,我們常常會遇到類型轉換的問題。比如 int 類型轉 int64,interface{} 轉 struct ,對一種類型取指針、解指針等等。今天在這篇文章中我們就來梳理一下,我們在 go 的日常使用中常碰到的幾個類型轉換場景。

go存在4種類型轉換分別為:斷言、強制、顯式、隱式。

通常說的類型轉換是指斷言,強制在日常不會使用到、顯示是基本的類型轉換、隱式使用到但是不會注意到。斷言、強制、顯式三類在go語法描述中均有說明,隱式是在日常使用過程中總結出來。

一、Assert 斷言

斷言通過判斷變量是否可以轉換成某一個類型

1、斷言(assert)

語法文檔:https://golang.google.cn/ref/spec#Type_assertions

expression必須是接口類型,且自身類型與Type類型相符。

expression.(Type)的返回值一般為兩個:value和ok,匹配成功ok為true,value有值,匹配失敗ok為false,value無值;也可以直接接受value一個返回值,不過失敗則直接panic:

func main() {
  var a interface{} = 100
  if aa, ok := a.(int); ok {
    fmt.Println(aa)
  }
}

一個簡單的斷言表達式:

var s = x.(T)

如果x不是nil,且x可以轉換成T類型,就會斷言成功,返回T類型的變量s。如果T不是接口類型,則要求x的類型就是T,如果T是一個接口,要求x實現了T接口。

如果斷言類型成立,則表達式返回值就是T類型的x,如果斷言失敗就會觸發panic。

上述表所示再斷言失敗就會panic,go提供了另外一種帶返回是否成立的斷言語法:

s, ok := x.(T)

該方法和第一種差不多一樣,但是ok會返回是否斷言成功不會出現panic,ok就表示是否是成功了。

2、類型switch

go語法種還提供了另外一種類型switch的斷言方法。

語法文檔:https://golang.google.cn/ref/spec#Type_switches

x斷言成了type類型,type類型具體值就是switch case的值,如果x成功斷言成了某個case類型,就可以執行那個case,此時i := x.(type)返回的i就是那個類型的變量了,可以直接當作case類型使用。

func main() {
  var t interface{} = 100
  switch i := t.(type) {
  case float32:
    fmt.Printf("i的類型%T i的值%v\n", i, i)
  case float64:
    fmt.Printf("i的類型%T i的值%v\n", i, i)
  case int:
    fmt.Printf("i的類型%T i的值%v\n", i, i)
  case bool:
    fmt.Printf("i的類型%T i的值%v\n", i, i)
  case string:
    fmt.Printf("i的類型%T i的值%v\n", i, i)
  default:
    fmt.Println("其他類型")
  }
}

二、強制類型轉換

強制類型轉換通過修改變量類型

該方法不常見,主要用於unsafe包和接口類型檢測,需要懂得go變量的知識。

1、unsafe

語法文檔:

https://golang.google.cn/ref/spec#Package_unsafe/

https://golang.org/ref/spec#Package_unsafe

本文檔僅大概說明一下,具體研究請求查找相關資料。

var f float64
bits = *(*uint64)(unsafe.Pointer(&f))

type ptr unsafe.Pointer
bits = *(*uint64)(ptr(&f))

var p ptr = nil

float64就強制轉換成uint64類型,float的地址就是一個值但是類型是float64,然后創建了一個uint64類型變量,地址值也是float64的地址值,兩個變量值相同類型不同,強制轉換了類型。

unsafe強制轉換是指針的底層操作了,用c的朋友就很熟悉這樣的指針類型轉換,利用內存對齊才能保證轉換可靠,例如int和uint存在符號位差別,利用unsafe轉換后值可能不同,但是在內存存儲二進制一模一樣。

2、接口類型檢測

例如下列代碼:

var _ Context = (*ContextBase)(nil)

nil的類型是nil地址值為0,利用強制類型轉換成了*ContextBase,返回的變量就是類型為*ContextBase地址值為0,然后Context=xx賦值如果xx實現了Context接口就沒事,如果沒有實現在編譯時期就會報錯,實現編譯期間檢測接口是否實現。

三、顯示類型轉換

語法文檔:https://golang.google.cn/ref/spec#Conversions

一個顯式轉換的表達式T(x) ,其中T是一種類型並且x是可轉換為類型的表達式T,例如:uint(666)

在以下任何一種情況下,變量x都可以轉換成T類型:

  • x可以分配成T類型。
  • 忽略struct標簽x的類型和T具有相同的基礎類型。
  • 忽略struct標記x的類型和T是未定義類型的指針類型,並且它們的指針基類型具有相同的基礎類型。
  • x的類型和T都是整數或浮點類型。
  • x的類型和T都是復數類型。
  • x的類型是整數或[]byte或[]rune,並且T是字符串類型。
  • x的類型是字符串,T類型是[]byte或[]rune。

例如下列代碼利用了規則進行轉換,規則實現可以參考reflect.Value.Convert方法邏輯:

int64(222)
[]byte("ssss")

type A int
A(2)

下面是demo

int(time.Now().Weekday()) //星期轉int
int(time.Now().Month())   //月份轉int

var a float64
a = 3.1
b := int(a) //float64轉int

var a int
a = 1
b := int64(a) //int轉int64
//這種類型轉換主要在切換同一基礎類型不同精度范圍時使用,比如我們要將 int 型轉為 int64 類型時。

四、隱式類型轉換

隱式類型轉換日常使用並不會感覺到,但是運行中確實出現了類型轉換,以下列出了三種。

1、JSON

Golang中大多數數據類型都可以轉化為有效的JSON文本,除了channel通道、complex復數、func函數等。

Golang指針可進行隱式轉換,表面進行的是指針序列化,內部會針對指針進行取值操作,實際還是針對所指的對象進行序列化。

2、組合間的重新斷言類型

type Reader interface {
    Read(p []byte) (n int, err error)
}
type ReadCloser interface {
    Reader
    Close() error
}
var rc ReaderClose
r := rc

ReaderClose接口組合了Reader接口,但是r=rc的賦值時還是類型轉換了,go使用系統內置的函數執行了類型轉換。以前遇到過類似接口組合類型的變量賦值,然后使用pprof和bench測試發現了這一細節,在接口類型轉移時浪費了一些性能。

3、相同類型間賦值

type Handler func()

func NewHandler() Handler {
    return func() {}
}

雖然type定義了Handler類型,但是Handler和func()是兩種實際類型,類型不會相等,使用反射和斷言均會出現兩種類型不同。

兩者類型不同驗證代碼:

package main

import (
    "fmt"
    "reflect"
)

type Handler func()

func a() Handler {
    return func() {}
}

func main() {
    var i interface{} = main
    _, ok := i.(func())
    fmt.Println(ok)
    _, ok = i.(Handler)
    fmt.Println(ok)
    fmt.Println(reflect.TypeOf(main) == reflect.TypeOf((*Handler)(nil)).Elem())
}

// true
// false
// false

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM