zoukankan      html  css  js  c++  java
  • 【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
    ", i, i)
      case float64:
        fmt.Printf("i的类型%T i的值%v
    ", i, i)
      case int:
        fmt.Printf("i的类型%T i的值%v
    ", i, i)
      case bool:
        fmt.Printf("i的类型%T i的值%v
    ", i, i)
      case string:
        fmt.Printf("i的类型%T i的值%v
    ", 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
    

      

  • 相关阅读:
    spring boot-17.RabbitMQ
    spring boot-16.使用redis做缓存
    spring boot-15.缓存
    spring boot-14.集成MyBatis
    spring boot-13.数据访问
    docker 安装完mysql 后客户端无法访问
    【python】string functions
    【转】澄清P问题、NP问题、NPC问题
    ubuntu中使用gensim+word2vec[备忘]
    ubuntu熟悉过程中遇到一些小问题记录一下
  • 原文地址:https://www.cnblogs.com/chenpingzhao/p/15418917.html
Copyright © 2011-2022 走看看