实际编码中,经常会出现类型转换的情况。
在C#
中有两种类型转换:隐式类型转换、显示类型转换(也作强制转换),其中隐式转换主要是在整型、浮点型之间的转换,将存储范围小的数据类型直接转换成存储范围大的数据类型,也就是小转大。
int a = 100;
double d = a; //将int类型转换为double类型
float f = 3.14f;
d = f; //将float类型转换为double类型
反之
int r ;
double rd=5.0;
r = rd;
Cannot implicitly convert type 'double' to 'int'. An explicit conversion exists (are you missing a cast?)
不能隐式转换double
至int
:因为进行转换可能会导致信息丢失,则编译器会要求执行显式转换,显式转换也称为强制转换:
int r ;
double rd=5.0;
r = (int)rd;
形为 (T)E
的强制转换表达式将表达式 E
的结果显式转换为类型 T
。
E
到类型 T
的显式转换,则发生编译时错误。有关支持的显式数值转换的完整列表,请参阅?内置数值转换一文的?显式数值转换部分。
对于引用类型,从基类型转换为派生类型,则必须进行显式强制转换:
// 创建派生类
Giraffe g = new Giraffe();
// 隐式转换为基类是安全的
Animal a = g;
// 需要显式转换才能强制把基类转换回派生类型
Giraffe g2 = (Giraffe)a; //如果a不是Giraffe,编译能通过,但在运行时会抛出异常
另外一种特殊的类型转换是?装箱和拆箱,这里就不做介绍了。
Go语言没有隐式转换,只有显式转换,说白了,任何一种类型转换,都需要开发者进行手动操作。
何谓简单转换?就是转换数据类型的方式很简单。
func main() {
a := 10
b := float32(a)
c := 50.5
d := int32(c)
e := int64(c)
fmt.Printf("%v %T\n", a, a)
fmt.Printf("%v %T\n", b, b)
fmt.Printf("%v %T\n", c, c)
fmt.Printf("%v %T\n", d, d)
fmt.Printf("%v %T\n", e, e)
}
输出
10 int
10 float32
50.5 float64
50 int32
50 int64
注意事项:
cannot convert xxx (type string) to type int64
;strconv
包提供的函数这一节就归纳一些在实际开发中,strconv包中经常用到的函数:
Itoa()
函数用于将int类型数据转换为对应的字符串表示,具体的函数签名如下。
func Itoa(i int) string
实际开发中,组合生成redis的key,key值为int类型的id
func main() {
var userid int
KeyPostVotedZSetPF := "post:voted:"
userid = 41654
key := KeyPostVotedZSetPF + strconv.Itoa(userid)
println(key)
}
输出
post:voted:41654
可以看到Itoa()
接收的是int
类型的参数,但是我们如果是通过雪花算法生成的用户id,是int64
,那Itoa
显然不能使用,FormatInt() 函数实现了将int64数据格式化为string,具体的函数签名如下:
func FormatInt(i int64, base int) string
func main() {
var userid int64
KeyPostVotedZSetPF := "post:voted:"
userid = 41654489498
key := KeyPostVotedZSetPF + strconv.FormatInt(userid, 10)
println(key)
}
输出
post:voted:41654489498
Format其实是有一系列函数,用于实现了将给定类型数据格式化为string类型数据的功能。
func FormatBool(b bool) string
func FormatInt(i int64, base int) string
func FormatUint(i uint64, base int) string
是FormatInt的无符号整型版本。
func FormatFloat(f float64, fmt byte, prec, bitSize int) string
函数将浮点数表示为字符串并返回。
bitSize表示f的来源类型(32:float32、64:float64),会据此进行舍入。
fmt表示格式:’f’(-ddd.dddd)、’b’(-ddddp±ddd,指数为二进制)、’e’(-d.dddde±dd,十进制指数)、’E’(-d.ddddE±dd,十进制指数)、’g’(指数很大时用’e’格式,否则’f’格式)、’G’(指数很大时用’E’格式,否则’f’格式)。
prec控制精度(排除指数部分):对’f’、’e’、’E’,它表示小数点后的数字个数;对’g’、’G’,它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。
有了Format系列函数把一些类型转换为string类型,那么反过来Parse系列函数就是用于将字符串类型转换为给定类型的值。
func ParseBool(str string) (value bool, err error)
返回字符串表示的bool值。它接受1、0、t、f、T、F、true、false、True、False、TRUE、FALSE;否则返回错误。
func ParseInt(s string, base int, bitSize int) (i int64, err error)
返回字符串表示的整数值,接受正负号。
base指定进制(2到36),如果base为0,则会从字符串前置判断,”0x”是16进制,”0”是8进制,否则是10进制;
bitSize指定结果必须能无溢出赋值的整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64;
返回的err是*NumErr类型的,如果语法有误,err.Error = ErrSyntax;如果结果超出类型范围err.Error = ErrRange。
func ParseUint(s string, base int, bitSize int) (n uint64, err error)
ParseUint
类似ParseInt
但不接受正负号,用于无符号整型。
func ParseFloat(s string, bitSize int) (f float64, err error)
有了int类型转字符串类型,就有字符串类型转int类型,Atoi()
函数用于将字符串类型的整数转换为int类型,函数签名如下。
func Atoi(s string) (i int, err error)
如果传入的字符串参数无法转换为int类型,就会返回错误。
s1 := "100"
i1, err := strconv.Atoi(s1)
if err != nil {
fmt.Println("can't convert to int")
} else {
fmt.Printf("type:%T value:%#v\n", i1, i1) //type:int value:100
}
除了上面常用的Format
系列函数和Parse
系列函数,还有Append系列函数
,用法与Format
类似,只是多了追加的操作。更多操作就看?官方文档吧
Go语言的空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
在接口值上的操作,用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型。
value, ok := x.(T)
x 表示一个接口类型的值(包括空接口),T 表示一个具体的类型(也可为接口类型)。
当变量是一个接口时,相比较具体类型时:
func main() {
var x interface{}
x = "I'm Garfield"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("Asserts Failed")
}
}
上面的示例中如果要多次断言就需要多个if
判断,Go语言中中提供了另外一种断言方法switch
:变量x
断言成了type
类型,type
类型具体值就是 switch case
的值,如果 x
成功断言成了某个 case
类型,就可以执行那个 case
,此时 i := x.(type)
返回的 i 就是那个类型的变量了,可以直接当作 case 类型使用。
func justifyType(x interface{}) {
switch i := x.(type) {
case int64:
fmt.Printf("x is a int64, is %v\n", i)
case string:
fmt.Printf("x is a string,value is %v\n", i)
case int:
fmt.Printf("x is a int is %v\n", i)
case bool, int32:
fmt.Printf("x is a bool or int32 is %v\n", i)
case nil:
fmt.Printf("x is a interface{} is %v\n", i)
default:
fmt.Println("unsupport type!")
}
func main() {
justifyType(nil)
justifyType("I'm Garfield")
justifyType(44)
justifyType(int64(516165161616))
justifyType(true)
justifyType(int32(1105020))
}
除此之外,开发者还可以像C#那样把实现了接口的实例赋值给接口变量,前面博文中介绍过利用编译器和匿名变量,判断结构体是否实现了接口,实质也就是利用这种方式做一个接口类型检测:
var _ IRouter = &Engine{}
类似的还有:
var _ IRouter = (*Engine)(nil)
nil的类型是 nil,地址值为0x0,利用强制类型转换成了 *Engine ,返回的变量就是类型为 *Engine 地址值为0x0,如果 *Engine 没有实现了 IRouter 接口,就会在编译时报错,上面两个看似不同的代码,其本质是一样的,目的也一致:实现在编译期间检测接口是否实现。
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/types/casting-and-type-conversions
https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/type-testing-and-cast#cast-expression
------------------- End -------------------