go之4种类型转换

2024-02-19 18:48:50 浏览数 (1)

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{}
代码语言:go复制
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
代码语言:go复制
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

*/

0 人点赞