52

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

 5 years ago
source link: https://www.jianshu.com/p/4eba89b2baf1?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
0.242019.04.15 16:34:33字数 2,922阅读 559

本文翻译自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中已解决。


Recommend

  • 73

    原文链接:https://0x0fff.com/hadoop-vs-mpp/最近,我听到很多关于这个话题的讨论。 同时,这是一个非常受欢迎的问题,客户在“大数据”领域没有太多的经验。 事实上,我不喜欢这个模糊的流行词,但这是客户通常来找我们,所以我必须使用它。 如果我们回顾5年前会发...

  • 82
    • 掘金 juejin.im 7 years ago
    • Cache

    笔记:隐式转换规则

    学习并背诵全文 类型 Undefined Null String Boolean Number 值 undefined null 所有字符串 true false

  • 55

    前言谁不希望自己写的代码像美女一样养眼,那我们聊聊架构吧,学好了架构才能让别的程序员"干"的爽 一). iOS架构的分类 在iOS中架构有很多,最常用的MVC,MVVM,MVP, 不常用的有VIPER 1.1)传统的MVC的架构是这样的: 上面是传统的MVC的架构虽说耦合极端严重,这也是在项...

  • 9
    • blog.popkx.com 4 years ago
    • Cache

    预分配文件类型:

    预分配文件类型: 发表于 2019-06-20 07:06:35...

  • 3

    统计软件 关联规则:R 与 SAS 的比较 关键词:R 语言...

  • 5

    QCOW2的几种预分配规则创建时间对比由 TaterLi2021年7月25日2021年7月25日 主要有以下几种策略,都在同一个SSD上测试. ...

  • 4

    规则引擎与ML模型的比较 - xLaszlo “基于规则的系统”通常是作为ML项目的良好起点。数据科学的重点是提出问题数据科学家的工...

  • 11
    • www.fly63.com 2 years ago
    • Cache

    Javascript 里的类型转换规则

    Javascript 里的类型转换是一个你永远绕不开的话题,不管你是在面试中还是工作写代码,总会碰到这类问题和各种的坑,所以不学好这个那是不行滴。关于类型转换我也看过不少的书和各种博...

  • 3
    • blog.p2hp.com 2 years ago
    • Cache

    apache 到nginx的rewrite规则转换

    apache 到nginx的rewrite规则转换 apache 到 ngi...

  • 4

    上文说到Unmanaged、BufferedBinary和BufferedString是NativeBuffering支持的三个基本数据类型,其实我们也可以说NativeBuffering只支持Unmanaged和IReadOnlyBufferedObject<T>...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK