go语言是静态类型语言,对变量的类型有严格的要求,因而在日常编写代码过程中,经常需要对变量的类型进行转换操作。这里介绍下go语言支持的4种类型转行方法。
一、显式转换: T(x)
顾名思义,显示转换需要在代码中明确的编写转换语句,语法为: T(x),其中x为变量,T为转换的目标类型
代码语言:go复制package main
func main() {
var a int = 123
var b int64
b = int64(a) // 即使是从窄往宽转换(从int转int64),也必须显示的转换
_ = b
}
这里需要注意的是,string和数值类型(int、float等等)的转换,需要通过strconv包来进行。strconv包的具体接口可以参照文档。
代码语言:go复制func main() {
num := 123
numStr := strconv.Itoa(num)
fmt.Println(numStr)
}
二、隐式转换
与显式转换相对应的,不需要开发人员编写转换代码,由编译器自动完成。
常见的隐式转换有:
- 数值常量初始化赋值、传参
- 结构体类型转接口类型和interface{}
package main
import (
"fmt"
)
type myBigInt int64 // 定义了一个新类型,myBigInt不是int64的别名,与int64是完全不同的类型
type People interface { // 定义接口
Say()
}
type Student struct { // 定义实现接口的结构体
Name string
}
func (s Student) Say() {
fmt.Println("my name is ", s.Name)
}
func TestA(i interface{}) interface{} { // 接收任意类似的函数
return i
}
func main() {
// 转接口
var p People = Student{"li si"} // 结构体自动转为接口
p.Say()
// 数值类型转换
var n int64 = 123
//var a myBigInt = n //这里必须显式转换,否则报错 Cannot use 'n' (type int64) as the type myBigInt
var a myBigInt = 123 // 数值常量初始化赋值可以自动转
// 具体类型转interface{},自动转换
TestA(n)
TestA(a)
TestA(p)
}
三、类型断言:newT, ok := x.(T)
转换语法为:newT, ok := x.(T), x为待转换的变量,T为目标类型,表达式有2个返回值:newT为转换成功后接收新类型的变量,ok标识转换是否成功。表达式也可以不接收ok这个返回值,形如:newT := x.(T),在不接收ok返回值的情况下,一旦断言失败会直接抛出panic。
代码语言:go复制package main
import (
"fmt"
)
type myBigInt int64
type People interface {
Say()
}
type Student struct {
Name string
}
func (s Student) Say() {
fmt.Println("my name is ", s.Name)
}
func main() {
var p People = Student{"li si"}
s, ok := p.(Student) // 接口类型断言结构体
if ok {
s.Say()
}else {
fmt.Println("断言p为Student类型失败")
}
/*
输出: my name is li si
*/
// 数值类型转换
var n int64 = 123
a := n.(myBigInt) // n为具体类型,非interface类型,无法断言: Invalid type assertion: n.(myBigInt) (non-interface type int64 on the left)
var a interface{} = 123 // 值为数值的interface{}类型在go内部一般使用float64来存储
b, ok := a.(myBigInt)
if ok {
fmt.Println("断言a为myBigInt成功: ", b)
}else {
fmt.Println("断言a为myBigInt失败")
}
/*
输出: 断言a为myBigInt失败
*/
c := a.(myBigInt) // 没有接收ok返回值,断言失败直接抛出panic。 panic: interface conversion: interface {} is int, not main.myBigInt
}
四、unsafe.Pointer强制转换
介绍这个方式之前,需要了解下go的指针。在go语言中有3类指针: 普通指针、uintptr、unsafe.Pointer。
- 普通指针 (*T): 存储类型T的对象的地址,可以使用&(取地址)*(根据地址取值),但不可计算
- uintptr:一个无符号的整型,可以保存一个指针地址,可以进行计算。uintptr无法持有对象,GC不把uintptr当指针,所以uintptr类型的目标会被回收
- unsafe.Ponter: 可以指向任意类型的指针,不能进行计算,不能读取内存存储的值
我们知道,指针的本质是一个uint类型,存储对象的地址(地址其实也是一个数字),在C语言中指针可以任意的计算、指向,因而存在比较大的风险。go语言为了避免指针被滥用、误用的风险,对指针做了限制(如类型校验、不可计算、不可跨类型转换等)。但同时也留了一个口子,允许指针进行跨类型转换,这便是unsafe.Pointer(从unsafe可以看出这个指针不安全的,使用不当容易出事)。
三类指针的特性如下:
- 普通指针(*T)不可计算、不可转换
- unsafe.Pointer可以和任意类型指针转换(*T, uintptr, unsafe.Pointer)
- uintptr可以计算
因而借助unsave.Pointer、uintptr,我们可以实现对普通指针进行计算和跨类型转换。这第四种类型转换便是借助unsafe.Pointer可任意转换的能力来实现。
- 跨类型类型转换: newPtr := (*newT)(unsave.Pointer(ptr)) 先转为unsafe.Pointer,再转为目标类型指针 ptr -> unsafe.Pointer -> *newT
- 指针计算: 先转为unsafe.Pointer,再转为uintptr,进行指针计算,再转为unsafe.Pointer,最后转为目标普通指针 ptr -> unsafe.Pointer -> uintptr -> 指针计算 -> unsafe.Pointer -> ptr
package main
import (
"fmt"
"unsafe"
)
type myBigInt int64
func main() {
var n int64 = 123
var ptr *int64 = &n
fmt.Println(ptr)
// 将ptr转为unsafe.Pointer,然后转为*myBigInt
myBigIntPtr := (*myBigInt)(unsafe.Pointer(ptr))
fmt.Println(myBigIntPtr, *myBigIntPtr)
}
/*
输出:
0xc0000a2000
0xc0000a2000 123
*/