本文翻译自Value Conversion, Assignment and Comparison Rules in Go
类型转换规则
注意:本文中的转换定义与Go规范不完全相同。Go规范中的转换意味着显式转换。本文中的转换包括显式和隐式转换。
在Go中,如果一个值v
可以被显式转换为类型T
,则转换可以表示为形式(T)(v)
。对于大多数情况,特别是T
是类型名称(标识符)的时候,形式可以简化为T(v)
。
我们应该知道的一个事实是,当某个值x
可以隐式转换为类型T
时,那么也代表着x
可以显式转换为类型T
。
1. 明确的转换规则
如果两种不同的类型都表示一个相同的类型,那么它们的值可以隐式转换为两种类型中的任意一种。
例如:
-
byte
和uint8
类型的值可以相互转换 -
rune
和int32
类型的值可以相互转换 -
[]byte
和[]uint8
类型的值可以相互转换
2. 基础类型相关的转换规则
给定一个非接口类型的值
x
和一个非接口类型T
,假设x
的类型是Tx
- 如果
Tx
和T
共享相同的基础类型(忽略结构体tags),那么x
可以被显式转换为T
- 如果
Tx
或T
是非定义类型,并且它们的基础类型相同(考虑结构体tags),那么x
可以隐式转为T
- 如果
Tx
和T
具有不同的基础类型,但是Tx
和T
都是非定义的指针类型,并且它们都指向相同的基础类型(忽略结构体tags),那么x
可以(并且必须)显式转换为T
例如:
package main
func main() {
// []int, IntSlice and MySlice share
// the same underlying type: []int
type IntSlice []int
type MySlice []int
var s = []int{}
var is = IntSlice{}
var ms = MySlice{}
var x struct{n int `foo`}
var y struct{n int `bar`}
// The two implicit conversions both doesn't work.
/*
is = ms // error
ms = is // error
*/
// Must use explicit conversions here.
is = IntSlice(ms)
ms = MySlice(is)
x = struct{n int `foo`}(y)
y = struct{n int `bar`}(x)
// Implicit conversions are okay here.
s = is
is = s
s = ms
ms = s
}
与指针相关的转换示例:
package main
func main() {
type MyInt int
type IntPtr *int
type MyIntPtr *MyInt
var pi = new(int) // the type of pi is *int
// ip and pi have the same underlying type,
// and the type of pi is non-defined, so
// the implicit conversion works.
var ip IntPtr = pi
// var _ *MyInt = pi // can't convert implicitly
var _ = (*MyInt)(pi) // ok, must explicitly
// Values of *int can't be converted to MyIntPtr
// directly, but can indirectly.
/*
var _ MyIntPtr = pi // can't convert implicitly
var _ = MyIntPtr(pi) // can't convert explicitly
*/
var _ MyIntPtr = (*MyInt)(pi) // ok
var _ = MyIntPtr((*MyInt)(pi)) // ok
// Values of IntPtr can't be converted to
// MyIntPtr directly, but can indirectly.
/*
var _ MyIntPtr = ip // can't convert implicitly
var _ = MyIntPtr(ip) // can't convert explicitly
*/
var _ MyIntPtr = (*MyInt)((*int)(ip)) // ok
var _ = MyIntPtr((*MyInt)((*int)(ip))) // ok
}
什么是基础类型?
在Go中,每种类型都有一个基础类型。规则:
- 对于内置的基本类型,基础类型就是它本身
- 对于所有的unsafe pointer类型,基础类型都是
unsafe.Pointer
- 对于一个非定义类型,也就是复合类型(比如结构体或者切片)来说,基础类型就是它本身
- 在类型声明中,新声明的类型和源类型具有相同的基础类型
例如:
// The underlying types of the following ones are both int. type ( MyInt int Age MyInt ) // The following new types have different underlying types. type ( IntSlice []int // underlying type is []int MyIntSlice []MyInt // underlying type is []MyInt AgeSlice []Age // underlying type is []Age ) // The underlying types of []Age, Ages, and AgeSlice // are all the non-defined type []Age. type Ages AgeSlice
如何跟踪给定用户声明类型的基础类型?规则是,当遇到内置基本类型,
unsafe.Pointer
或非定义类型时,则停止跟踪。以上面的类型声明为例,让我们跟踪它们的基础类型。
MyInt → int
Age → MyInt → int
IntSlice → []int
MyIntSlice → []MyInt →[]int
AgeSlice → []Age →[]MyInt→[]int
Ages → AgeSlice → []Age →[]MyInt→[]int在Go中:
- 基础类型为
bool
的类型称为布尔类型- 基础类型为任意内置整数的类型称为整型
- 基础类型为
float32
或float64
的类型称为浮点型- 基础类型为
complex64
或complex128
的类型称为复数类型- 整数,浮点数和复数类型统称为数字类型
- 基础类型为
string
的类型称为字符串类型
3. 通道特定转换规则
假设
Tx
是双向信道类型,T
也是信道类型(双向或非双向),如果Tx
和T
具有相同的元素类型,并且Tx
或T
是非定义类型,则x
可以隐式转换为T
例如:
package main
func main() {
type C chan string
type C1 chan<- string
type C2 <-chan string
var ca C
var cb chan string
cb = ca // ok, same underlying type
ca = cb // ok, same underlying type
// The 4 lines compile okay for this 2nd rule.
var _, _ chan<- string = ca, cb // ok
var _, _ <-chan string = ca, cb // ok
var _ C1 = cb // ok
var _ C2 = cb // ok
// Values of C can't be converted
// to C1 and C2 directly.
/*
var _ = C1(ca) // compile error
var _ = C2(ca) // compile error
*/
// Values of C can be converted
// to C1 and C2 indirectly.
var _ = C1((chan<- string)(ca)) // ok
var _ = C2((<-chan string)(ca)) // ok
var _ C1 = (chan<- string)(ca) // ok
var _ C2 = (<-chan string)(ca) // ok
}
4. 接口实现相关的转换规则
给定一个值
x
和一个接口类型I
,如果x
的类型(或默认类型)是Tx
并且Tx
实现了I
,那么x
可以被隐式转换为类型I
。转换的结果是一个接口值(类型I
),它封装了:
- 如果
Tx
是非接口类型,则为x
的副本- 如果
Tx
是接口类型,则为x
动态值的副本
给定一个接口值
x
,其动态类型为T
,x
可以通过类型断言语法x.(T)
安全地转换为T
给定一个接口值
x
和一个接口类型I
,如果x
的动态类型实现了I
,则x
可以通过类型断言语法x.(I)
安全地转换为类型I
5. 无类型值转换规则
如果无类型值可以表示为类型
T
的值,那么可以将无类型值隐式转换为类型T
例如:
package main
func main() {
var _ []int = nil
var _ map[string]int = nil
var _ chan string = nil
var _ func()() = nil
var _ *bool = nil
var _ interface{} = nil
var _ int = 123.0
var _ float64 = 123
var _ int32 = 1.23e2
var _ int8 = 1 + 0i
}
6. 常量转换规则
(这条规则与上一条重叠)
通常,转换常量仍会产生一个常量。(除了将常量字符串转换为下面第8条规则中描述的字节切片或rune切片外)
给定一个常量值
x
和一个类型T
,如果x
可以表示为类型T
的值,那么x
可以被显式转换为类型T
的值。特别是如果x
是无类型值,则x
可以隐式转换为类型T
的值
例如:
package main
func main() {
const I = 123
const I1, I2 int8 = 0x7F, -0x80
const I3, I4 int8 = I, 0.0
const F = 0.123456789
const F32 float32 = F
const F32b float32 = I
const F64 float64 = F
const F64b = float64(I3) // must be explicit
const C1, C2 complex64 = F, I
const I5 = int(C2) // must be explicit
}
7. 非常数转换规则
非常量浮点数和整数可以被显式转换为任意浮点类型和整型
非常量复数值可以被显式转换为任意复数类型
注意:
- 复数的非常量值无法转换为浮点和整数类型
- 浮点数和整数不能被转换为复数类型
- 在非常量转换中允许数据溢出和舍入。将浮点数转换为整数时,会丢弃小数(截断为零)
例如:
package main
import "fmt"
func main() {
var a, b = 1.6, -1.6 // both are float64
fmt.Println(int(a), int(b)) // 1 -1
var i, j int16 = 0x7FFF, -0x8000
fmt.Println(int8(i), uint16(j)) // -1 32768
var c1 complex64 = 1 + 2i
var _ = complex128(c1)
}
8. 字符串转换规则
如果值的类型(或默认类型)是整数类型,则可以将值显式转换为字符串类型
例如:
string(65) // "A"
string(-1) // "\uFFFD"
string(0xFFFD) // "\uFFFD"
string(0x2FFFFFFFF) // "\uFFFD"
一个字符串值可以被显式转换为一个基础类型为
[]byte
的切片(a.k.auint8
),反之亦然
一个字符串值可以被显式转换为一个基础类型为
[]rune
的切片(a.k.a[]int32
),反之亦然
9. unsafe pointers 转换规则
任意类型的指针值都可以显式转换为基础类型为
unsafe.Pointer
的类型,反之亦然
可以将
uintptr
类型的值显式转换为基础类型为unsafe.Pointer
的类型,反之亦然
值的分配规则
可以将分配视为隐式转换。隐式转换规则列在上一节中的所有转换规则中。
除了这些规则之外,赋值中的目标值必须是可寻址的,映射索引表达式或空白标识符。
在赋值中,源值将复制到目标值。准确地说,源值的直接部分被复制到目标值。
注意,参数传递和结果返回实际上都是在赋值。
值比较规则
Go 规范:
In any comparison, the first operand must be assignable to the type of the second operand, or vice versa.
因此,比较规则很像分配规则。换句话说,如果其中一个操作数可以隐式转换为另一个操作数的类型,则两个值是可比较的。对吗?几乎是,因为上述基本比较规则有一个例外。
如果比较中的两个操作数之一是接口类型,而另一个操作数是一个不可比较类型的非接口值(应该实现了前一个操作数的接口类型),则比较规则无效,即使非接口值可以隐式转换为接口类型
可比较类型与不可比较类型
当前(Go 1.12),以下类型不支持比较操作(使用==
或!=
运算符)
- 切片类型
- map类型
- 函数类型
- 任何字段为不可比较类型的结构体类型,以及任何元素类型为不可比较类型的数组类型
上面列出的类型称为不可比较类型,所有其他类型称为可比较类型。编译器禁止比较两种不可比较类型的值。
任何map类型的key类型必须是可比较类型
请注意,虽然切片/map/函数类型的值不可比较,但可以将它们与无类型的nil
值进行比较。
上述基本规则并未涵盖所有情况。如果比较中的两个操作数都是无类型(常量)值,那该怎么办?附加规则很简单:
- 无类型的布尔值可以与无类型的布尔值进行比较
- 无类型数值可以与无类型数值进行比较
- 无类型字符串值可以与无类型字符串值进行比较
比较两个无类型数值的结果服从直觉。
注意,一个无类型的nil
值不能和另一个无类型的nil
值进行比较。
任意比较操作的结果都是一个无类型的布尔值。
以下示例展示了一些与不可比较类型相关的比较:
package main
// Some variables of uncomparable types.
var s []int
var m map[int]int
var f func()()
var t struct {x []int}
var a [5]map[int]int
func main() {
// The following lines fail to compile.
/*
_ = s == s
_ = m == m
_ = f == f
_ = t == t
_ = a == a
_ = nil == nil
_ = s == interface{}(nil)
_ = m == interface{}(nil)
_ = f == interface{}(nil)
*/
// The following lines compile okay.
_ = s == nil
_ = m == nil
_ = f == nil
_ = 123 == interface{}(nil)
_ = true == interface{}(nil)
_ = "abc" == interface{}(nil)
}
两个值如何比较?
假设两个值是可比较的,并且它们具有相同的类型T
。(如果它们具有不同的类型,则其中一个必须可隐式转换为另一个的类型。这里我们不考虑两个值都是无类型的情况)
- 如果
T
是布尔类型,那么只有当两者都为true
或都为false
时,这两个值才相等 - 如果
T
是整数类型,那么只有当两者在内存中具有相同表示的情况下,这两个值才相等 - 如果
T
是浮点类型,那么当满足以下条件时它们相等:- 3.1 它们都是
+Inf
- 3.2 它们都是
-Inf
- 3.3 它们中的每一个都是
-0.0
或+0.0
- 3.4 它们都不是
NaN
,并且它们在内存中具有相同的字节表示
- 3.1 它们都是
- 如果
T
是复数类型,那么只有当它们的实部(作为浮点值)和虚部(作为浮点值)都相等时,这两个值才相等 - 如果
T
是指针类型(safe或unsafe),那么只有它们指向的内存地址相同时,这两个值才相等 - 如果
T
是通道类型,如果它们都引用相同的底层内部通道结构或者它们都是nil
,这两个值才相等 - 如果
T
是结构体类型,那么将比较两个结构体每个对应字段的值 - 如果
T
是数组类型,那么将比较两个数组的每个对应元素的值 - 如果
T
是接口类型,请参考how two interface values are compared - 如果
T
是字符串类型,请参考how two string values are compared
请注意,将两个具有相同非比较动态类型的接口进行比较时会产生panic,以下是一些比较中会出现panic的例子:
package main
func main() {
type T struct {
a interface{}
b int
}
var x interface{} = []int{}
var y = T{a: x}
var z = [3]T{}
// Each of the following line can produce a panic.
_ = x == x
_ = y == y
_ = z == z
}
请注意,在任意版本的Go SDK中,包含z
的两行代码都可以通过go build
和go install
命令,但是在Go SDK 1.9和Go SDK 1.10中运行go run
命令会失败,这是一个已知问题,在Go SDK 1.11中已解决。