Go 的类型转换

magichan · 2018-08-06 20:42:59 · 5363 次点击 · 预计阅读时间 9 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2018-08-06 20:42:59 的文章,其中的信息可能已经有所发展或是发生改变。

有时候你可能需要将变量转换为其他类型。Golang 不容许随意处理这种转换,转换是由类型系统的强制保证的某些规则。在这篇文章中,我们将讨论哪些转换是可能的,哪些是不可能,以及什么时候进行转换是有价值的。

Go 是一门强类型语言。它在类型上是严格的,编译期间会报告类型错误。

package main
import "fmt"
func main() {
    monster := 1 + "2"
    fmt.Printf("monster: %v\n", monster)
}
> go build
# github.com/mlowicki/lab
./lab.go:6: cannot convert "2" to type int
./lab.go:6: invalid operation: 1 + "2" (mismatched types int and string)

JavaScript 是弱类型语言的一种,让我们看看它的实际效果:

var monster = 1 + "foo" + function() {};
console.info("type:", typeof monster)
console.info("value:", monster);

我将数字,字符串甚至函数加在一起,这似乎是件奇怪的事情。但不用担心,JavaScript 会无报错地为你处理这些事情。

type: string
value: 1foofunction () {}

在特定的情况,可能需要将一个变量转为其他类型,例如将它作为函数参数传递,或是放进某个表达式中。

func f(text string) {
    fmt.Println(text)
}
func main() {
    f(string(65))  // 整型常量转换为字符串。
}

函数调用的表达式是类型转换的常见情况, 下面我们会多次看到类似的转换。上面的代码是能够正常运行的,但是如果移除类型转换:

f(65)

会导致一个编译时错误:"cannot use 65 (type int) as type string in argument to f" (无法将整数类型的 65 作为字符串类型参数给 f )

底层类型(Underlying type)

字符串,布尔值,数字或者字面量类型的底层类型仍是他们本身,其他情况下,类型声明定义了底层类型:

type A string             // string
type B A                  // string
type C map[string]float64 // map[string]float64 (type literal)
type D C                  // map[string]float64
type E *D                 // *D

(注释里是对应的底层类型) 如果底层类型是相同的,那么类型转换时百分百有效的。

package main
type A string
type B A
type C map[string]float64
type C2 map[string]float64
type D C
func main() {
    var a A = "a"
    var b B = "b"
    c := make(C)
    c2 := make(C2)
    d := make(D)
    a = A(b)
    b = B(a)
    c = C(c2)
    c = C(d)
    var _ map[string]float64 = map[string]float64(c)
}

上面的程序不会有任何的编译问题。底层类型的定义不能是递归的( Definition of underlying types isn’t recursive)

译按:

关于底层类型的定义不能是递归的情况,译者对这种说法保持怀疑。 底层类型的定义要么解释到内置类型(int, int64, float, string, bool...), 要么递归解释到 unnamed type 的。例如

  • B->A->string, string 为内置类型,解释停止, B 的底层类型为 string;
  • U->T->map[S]float64, map[S]float64 为 unnamed type, 解释停止,U 的底层类型为 map[S]float64。
    type A string             // string
    type B A                  // string
    type S string             // string
    type T map[S]float64      // map[S]float64
    type U T                  // map[S]float64
    
type S string
type T map[S]float64

....

var _ map[string]float64 = make(T)

上面的程序会在编译期间报错:

cannot use make(T) (type T) as type map[string]float64 in assignment

赋值错误的发生,是因为 T 的底层类型不是 map[string]float64 而是 map[S]float64。类型转换也会报错:

var _ map[string]float64 = (map[string]float64)(make(T))

上面的代码会在编译时导致如下错误:

cannot convert make(T) (type T) to type map[string]float64

可赋值性(Assignability)

Go 语言规范提出可赋值性(Assignability)的概念,它定义了什么时候一个变量 v 能够被赋值给 T 类型的变量。让我们在代码中了解它的一个规则:赋值时,两者应该具有相同的底层类型,并且至少有一个不是 named 类型。

package main
import "fmt"
func f(n [2]int) {
    fmt.Println(n)
}
type T [2]int
func main() {
    var v T
    f(v)
}

程序输出 “[0 0]”。在可赋值性规则许可的范围内,所有的类型转换都是可行的。程序员能用这种方式清晰地表达他的具体的想法:

f([2]int(v))

上面的调用方法会和之前的得到一样的结果。关于可赋值性更多信息可以在之前的文章中找到。

类型转换的第一个规则(具有相同的底层类型)和可赋值规则的一条规则是重合的 - 当底层类型是相同的时候,至少有一个的类型是 unnmaed 类型(这一节的第一个例子)。较弱的规则会影响更严格的规则。因此当类型转换时,只需要底层类型保持一致,是否是 named/unnamed 类型并不重要。

常量

常量 v 能够被转换为类型 T, 当 v 能被 T 类型的变量表示时。

a := uint32(1<<32 – 1)
//b := uint32(1 << 32) // constant 4294967296 overflows uint32
c := float32(3.4e38)
//d := float32(3.4e39) // constant 3.4e+39 overflows float32
e := string("foo")
//f := uint32(1.1) // constant 1.1 truncated to integer
g := bool(true)
//h := bool(1) // convert 1 (type int) to type bool
i := rune('ł')
j := complex128(0.0 + 1.0i)
k := string(65)

对于常量更深入的介绍可以在官方博客中找到。

数字类型

浮点数(floating-point number) -> 整数(integer)

var n float64 = 1.1
var m int64 = int64(n)
fmt.Println(m)

小数部分被移除,因此代码输出 ”1“。

对于其他转换:

  • 浮点数 -> 浮点数,
  • 整数 -> 整数,
  • 整数 -> 浮点数,
  • 复数 -> 复数。

变量会被四舍五入至目标精度:

var a int64 = 2 << 60
var b int32 = int32(a)
fmt.Println(a)         // 2305843009213693952
fmt.Println(b)         // 0
a = 2 << 30
b = int32(a)
fmt.Println(a)         // 2147483648
fmt.Println(b)         // -2147483648
b = 2 << 10
a = int64(b)
fmt.Println(a)         // 2048
fmt.Println(b)         // 2048

指针

可赋值性(Assignability) 以和处理其他类型一样的方式处理指针类型。

package main
import "fmt"
type T *int64
func main() {
    var n int64 = 1
    var m int64 = 2
    var p T = &n
    var q *int64 = &m
    p = q
    fmt.Println(*p)
}

程序正常工作并且输出 ”2“,这依赖于已经被讨论过的可赋值性规则(assignability rule)int64T* 的底层类型是相同的,并且 int64* 是 unnamed 类型。类型转换则更宽松一些。对于 unnamed 指针类型,指针的基类型(base type)具有相同的底层类型即可转换。

译按: 指针的基类型(base type)为指针所指向变量的类型,例如 p *int, p 的基类型为 int。

package main
import "fmt"
type T int64
type U W
type W int64
func main() {
    var n T = 1
    var m U = 2
    var p *T = &n
    var q *U = &m
    p = (*T)(q)
    fmt.Println(*p)
}

*T 应该在括号内,否则他会被理解成 *(T(q))

和之前的程序一样,输出 “2”。因为 UT 的在作为U* 和 T* 的基类型的同时,他们的底层类型是相同的。 如下的赋值操作:

p = q

是不会成功的,因为它尝试处理两种不同的底层类型: T* 和 U*。作为练习,让我们稍微改变声明,看会发生什么

type T *int64
type U *W
type W int64
func main() {
    var n int64 = 1
    var m W = 2
    var p T = &n
    var q U = &m
    p = T(q)
    fmt.Println(*p)
}

UW 的声明已经被改变。思考一下,会发生什么?

编译器在以下位置报一个错误 “cannot convert q (type U) to type T”(无法将 q (类型 U) 转换为类型 T):

p = T(q)

这是因为 p 的底层类型是 int64,而 q* 的是 W。q 的类型是 named(U*),因此获取指针基类型的底层类型的规则并不适用在这里。

字符串

整数 -> 字符串

传递数字 Nstring 内置地将 N 转化为 UTF-8 编码的字符串,该字符串是 N 表达的字符组成。

fmt.Printf("%s\n", string(65))
fmt.Printf("%s\n", string(322))
fmt.Printf("%s\n", string(123456))
fmt.Printf("%s\n", string(-1))

输出:

A
ł
�
�

前两个转换使用完全有效的码位。也许你会好奇为什么后两行显示奇怪的符号。它是一个替换字符(replacement character),是称为 specials Unicode 区段的一员。它的编码为 \uFFFD ( 更多信息)

对 strings 的简要介绍

Strings 基本上是字节的切片:

text := "abł"
for i := 0; i < len(text); i++ {
    fmt.Println(text[i])
}

输出:

97
98
197
130

97 和 98 是 UTF-8 编码的 “a” 和 “b“ 字符。第三和第四行的输出是字符 “ł” 的 UTF8 编码,该编码占据了两个字节的空间。

range 循环有助于迭代 Unicode 定义的码位( 码位在 Golang 中被称为 rune )

text := "abł"
for _, s := range text {
    fmt.Printf("%q %#v\n", s, s)
}

输出:

'a' 97
'b' 98
'ł' 322

想了解更多类似 %q%v 这样的占位符,可以看 fmt 包的文档

更多的讨论可在 《Golang的字符串,字节,rune 和字符》。在这个快速解释之后,在字符串和字节切片之间的转换应该不会再难以理解。

string ↔ slice of bytes

bytes := []byte("abł")
text := string(bytes)
fmt.Printf("%#v\n", bytes) // []byte{0x61, 0x62, 0xc5, 0x82}
fmt.Printf("%#v\n", text)  // "abł"

切片由被转换 string 的 utf8 编码字节组成。

string ↔ slice of runes

runes := []rune("abł")
fmt.Printf("%#v\n", runes)         // []int32{97, 98, 322}
fmt.Printf("%+q\n", runes)         // ['a' 'b' '\u0142']
fmt.Printf("%#v\n", string(runes)) // "abł"

从被转换 string 中创建的切片是由 Unicode 编码的码位( rune )组成。

结尾

如果你喜欢这篇文章,请关注,以便你收到最新的文章推送消息。

资料


via: https://medium.com/golangspec/conversions-in-go-4301e8d84067

作者:Michał Łowicki  译者:magichan  校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

5363 次点击  
加入收藏 微博
1 回复  |  直到 2018-08-07 10:22:18
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传