一尘不染

在Go中解释类型断言

go

我正在阅读《 Go编程语言》x.(T)类型断言,但不理解它们。

我了解有不同的方案:

  • T是具体类型或接口
  • 可以返回一个(确定值?)或两个(确定)值

这是我不明白的:

  • 我为什么要使用它们?
  • 他们到底返回什么?

我也用谷歌搜索这个话题,但仍然不明白。


阅读 306

收藏
2020-07-02

共1个答案

一尘不染

简短答案

一行:

x.(T)断言这x不是nil并且存储的值x是type T

我为什么要使用它们:

  • 检查x为零
  • 检查它是否可转换(断言)为另一种类型
  • 转换(断言)为另一种类型

他们到底返回什么:

  • t := x.(T)=> t是类型T;如果x为零,则表示恐慌。

  • t,ok := x.(T)=>如果x是零或不类型T=> okfalse否则oktruet是类型的T


详细说明

假设您需要计算4种不同形状的面积:圆形,正方形,矩形和三角形。您可以使用称为的新方法来定义新类型Area(),如下所示:

type Circle struct {
    Radius float64
}
func (t Circle) Area() float64 {
    return math.Pi * t.Radius * t.Radius
}

对于Triangle

type Triangle struct {
    A, B, C float64 // lengths of the sides of a triangle.
}
func (t Triangle) Area() float64 {
    p := (t.A + t.B + t.C) / 2.0 // perimeter half
    return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C))
}

对于Rectangle

type Rectangle struct {
    A, B float64
}

func (t Rectangle) Area() float64 {
    return t.A * t.B
}

对于Square

type Square struct {
    A float64
}
func (t Square) Area() float64 {
    return t.A * t.A
}

在这里,您有Circle,其半径为1.0,其侧面具有其他形状:

shapes := []Shape{
    Circle{1.0},
    Square{1.772453},
    Rectangle{5, 10},
    Triangle{10, 4, 7},
}

有趣!我们如何将它们全部收集在一个地方?
首先,您需要Shape interface将它们全部收集成一片形状[]Shape

type Shape interface {
    Area() float64
}

现在,您可以像这样收集它们:

shapes := []Shape{
    Circle{1.0},
    Square{1.772453},
    Rectangle{5, 10},
    Triangle{10, 4, 7},
}

毕竟,CircleShapeTriangleShape太。
现在,您可以使用单个语句打印每个形状的区域v.Area()

for _, v := range shapes {
    fmt.Println(v, "\tArea:", v.Area())
}

Area()所有形状之间的通用接口也是如此。现在,我们如何使用上述方法来计算和调用像三角形的角之类的不常见方法shapes

func (t Triangle) Angles() []float64 {
    return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)}
}
func angle(a, b, c float64) float64 {
    return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi
}

现在是时候Triangle从上方提取shapes

for _, v := range shapes {
    fmt.Println(v, "\tArea:", v.Area())
    if t, ok := v.(Triangle); ok {
        fmt.Println("Angles:", t.Angles())
    }
}

使用t, ok := v.(Triangle)我们要求的类型断言,这意味着我们要求编译器尝试将vtype 转换Shape为type
Triangle,以便如果成功,ok则将为true否则false,然后如果成功,则调用t.Angles()计算三角形的三个角度。

这是输出:

Circle (Radius: 1)  Area: 3.141592653589793
Square (Sides: 1.772453)    Area: 3.1415896372090004
Rectangle (Sides: 5, 10)    Area: 50
Triangle (Sides: 10, 4, 7)  Area: 10.928746497197197
Angles: [128.68218745348943 18.194872338766785 33.12294020774379]

以及整个工作示例代码:

package main

import "fmt"
import "math"

func main() {
    shapes := []Shape{
        Circle{1.0},
        Square{1.772453},
        Rectangle{5, 10},
        Triangle{10, 4, 7},
    }
    for _, v := range shapes {
        fmt.Println(v, "\tArea:", v.Area())
        if t, ok := v.(Triangle); ok {
            fmt.Println("Angles:", t.Angles())
        }
    }
}

type Shape interface {
    Area() float64
}
type Circle struct {
    Radius float64
}
type Triangle struct {
    A, B, C float64 // lengths of the sides of a triangle.
}
type Rectangle struct {
    A, B float64
}
type Square struct {
    A float64
}

func (t Circle) Area() float64 {
    return math.Pi * t.Radius * t.Radius
}

// Heron's Formula for the area of a triangle
func (t Triangle) Area() float64 {
    p := (t.A + t.B + t.C) / 2.0 // perimeter half
    return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C))
}
func (t Rectangle) Area() float64 {
    return t.A * t.B
}

func (t Square) Area() float64 {
    return t.A * t.A
}

func (t Circle) String() string {
    return fmt.Sprint("Circle (Radius: ", t.Radius, ")")
}
func (t Triangle) String() string {
    return fmt.Sprint("Triangle (Sides: ", t.A, ", ", t.B, ", ", t.C, ")")
}
func (t Rectangle) String() string {
    return fmt.Sprint("Rectangle (Sides: ", t.A, ", ", t.B, ")")
}
func (t Square) String() string {
    return fmt.Sprint("Square (Sides: ", t.A, ")")
}

func (t Triangle) Angles() []float64 {
    return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)}
}
func angle(a, b, c float64) float64 {
    return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi
}

另请参阅:

类型断言

对于具有接口类型和类型T的表达式x,主要表达式

x.(T)

断言x不为nil,并且x中存储的值的类型为T。符号x。(T)称为类型断言。

更准确地说,如果T不是接口类型,则x。(T)断言x的动态类型与T的类型相同。在这种情况下,T必须实现x的(接口)类型。否则,类型断言无效,因为x不可能存储类型T的值。如果T是接口类型,则x。(T)断言x的动态类型实现了接口T。

如果类型断言成立,则表达式的值为存储在x中的值,其类型为T。 如果类型断言为false,则会发生运行时恐慌。
换句话说,即使x的动态类型仅在运行时才知道,但在正确的程序中x。(T)的类型还是T。

var x interface{} = 7  // x has dynamic type int and value 7
i := x.(int)           // i has type int and value 7

type I interface { m() }
var y I
s := y.(string)        // illegal: string does not implement I (missing

method m)
r := y.(io.Reader) // r has type io.Reader and y must implement both
I and io.Reader

在特殊形式的赋值或初始化中使用的类型断言

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)

产生另一个无类型的布尔值。如果断言成立,则ok的值为true。否则为false,并且v的值为T类型的零值。 在这种情况下,不会发生运行时恐慌


编辑

问题x.(T)当T是an interface{}而不是具体类型时,断言返回什么?

它断言x不是nil,并且x中存储的值是T类型。

例如,此恐慌(编译:成功,运行:)panic: interface conversion: interface is nil, not interface {}

package main

func main() {
    var i interface{} // nil
    var _ = i.(interface{})
}

这可行(运行:确定):

package main

import "fmt"

func main() {
    var i interface{} // nil
    b, ok := i.(interface{})
    fmt.Println(b, ok) // <nil> false

    i = 2
    c, ok := i.(interface{})
    fmt.Println(c, ok) // 2 true

    //var j int = c // cannot use c (type interface {}) as type int in assignment: need type assertion
    //fmt.Println(j)
}

输出:

<nil> false
2 true

注意: 这里c是类型interface {}而不是int

请参阅以下带有注释输出的工作示例代码:

package main

import "fmt"

func main() {
    const fm = "'%T'\t'%#[1]v'\t'%[1]v'\t%v\n"
    var i interface{}
    b, ok := i.(interface{})
    fmt.Printf(fm, b, ok) // '<nil>'    '<nil>' '<nil>' false

    i = 2
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'int'  '2' '2' true

    i = "Hi"
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'string'   '"Hi"'  'Hi'    true

    i = new(interface{})
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // '*interface {}'    '(*interface {})(0xc042004330)' '0xc042004330'  true

    i = struct{}{}
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'struct {}'    'struct {}{}'   '{}'    true

    i = fmt.Println
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'func(...interface {}) (int, error)'   '(func(...interface {}) (int, error))(0x456740)'    '0x456740'  true

    i = Shape.Area
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'func(main.Shape) float64' '(func(main.Shape) float64)(0x401910)'  '0x401910'  true
}

type Shape interface {
    Area() float64
}
2020-07-02