Go中的类型转换,分配和比较规则

本文翻译自Value Conversion, Assignment and Comparison Rules in Go

类型转换规则

注意:本文中的转换定义与Go规范不完全相同。Go规范中的转换意味着显式转换。本文中的转换包括显式和隐式转换。

在Go中,如果一个值v可以被显式转换为类型T,则转换可以表示为形式(T)(v)。对于大多数情况,特别是T是类型名称(标识符)的时候,形式可以简化为T(v)

我们应该知道的一个事实是,当某个值x可以隐式转换为类型T时,那么也代表着x可以显式转换为类型T

1. 明确的转换规则

如果两种不同的类型都表示一个相同的类型,那么它们的值可以隐式转换为两种类型中的任意一种。

例如:

  • byteuint8类型的值可以相互转换
  • runeint32类型的值可以相互转换
  • []byte[]uint8类型的值可以相互转换

2. 基础类型相关的转换规则

给定一个非接口类型的值x和一个非接口类型T,假设x的类型是Tx

  • 如果TxT共享相同的基础类型(忽略结构体tags),那么x可以被显式转换为T
  • 如果TxT是非定义类型,并且它们的基础类型相同(考虑结构体tags),那么x可以隐式转为T
  • 如果TxT具有不同的基础类型,但是TxT都是非定义的指针类型,并且它们都指向相同的基础类型(忽略结构体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的类型称为布尔类型
  • 基础类型为任意内置整数的类型称为整型
  • 基础类型为float32float64的类型称为浮点型
  • 基础类型为complex64complex128的类型称为复数类型
  • 整数,浮点数和复数类型统称为数字类型
  • 基础类型为string的类型称为字符串类型

3. 通道特定转换规则

假设Tx是双向信道类型,T也是信道类型(双向或非双向),如果TxT具有相同的元素类型,并且TxT是非定义类型,则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,其动态类型为Tx可以通过类型断言语法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.a uint8),反之亦然

一个字符串值可以被显式转换为一个基础类型为[]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。(如果它们具有不同的类型,则其中一个必须可隐式转换为另一个的类型。这里我们不考虑两个值都是无类型的情况)

  1. 如果T是布尔类型,那么只有当两者都为true或都为false时,这两个值才相等
  2. 如果T是整数类型,那么只有当两者在内存中具有相同表示的情况下,这两个值才相等
  3. 如果T是浮点类型,那么当满足以下条件时它们相等:
    • 3.1 它们都是+Inf
    • 3.2 它们都是-Inf
    • 3.3 它们中的每一个都是-0.0+0.0
    • 3.4 它们都不是NaN,并且它们在内存中具有相同的字节表示
  4. 如果T是复数类型,那么只有当它们的实部(作为浮点值)和虚部(作为浮点值)都相等时,这两个值才相等
  5. 如果T是指针类型(safe或unsafe),那么只有它们指向的内存地址相同时,这两个值才相等
  6. 如果T是通道类型,如果它们都引用相同的底层内部通道结构或者它们都是nil,这两个值才相等
  7. 如果T是结构体类型,那么将比较两个结构体每个对应字段的值
  8. 如果T是数组类型,那么将比较两个数组的每个对应元素的值
  9. 如果T是接口类型,请参考how two interface values are compared
  10. 如果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 buildgo install命令,但是在Go SDK 1.9和Go SDK 1.10中运行go run命令会失败,这是一个已知问题,在Go SDK 1.11中已解决。

你可能感兴趣的:(Go中的类型转换,分配和比较规则)