簡體   English   中英

在Go中解釋類型斷言

[英]Explain Type Assertions in Go

我正在閱讀Go Go Programming Language類型斷言x.(T)並且不理解它們。

我知道有不同的場景:

  • T是具體類型或接口
  • 可以返回一個(斷言值?)或兩個(ok)值

這是我不明白的:

  • 我為什么要用它們?
  • 他們究竟回歸了什么?

我也搜索了這個話題,仍然不明白。

簡短的回答

在一行中:

x.(T)斷言x不是nil,而x存儲的值是T類型。

我為什么要用它們:

  • 檢查x是否為零
  • 檢查它是否可轉換(斷言)為另一種類型
  • 將(斷言)轉換為另一種類型

究竟是什么回報:

  • t := x.(T) => t是T型; 如果x為零,則會發生恐慌。

  • t,ok := x.(T) =>如果x為n或者類型為T => ok則為false否則oktruetT類型。


詳細解釋

想象一下,你需要計算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},
}

畢竟, Circle是一個ShapeTriangle也是一個Shape
現在,您可以使用單個語句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
}

現在是時候從上面的shapes提取Triangle了:

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)我們請求類型斷言,這意味着我們要求編譯器嘗試將類型為Shape v轉換為類型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
}

另見:

輸入斷言

對於接口類型的表達式x和類型T,主表達式

 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的零值。 在這種情況下,不會發生運行時混亂


編輯

問題 :當T是interface{}而不是具體類型時,斷言x.(T)返回什么?
答案

它聲明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
}

常用用例:檢查返回的錯誤是否為T類型。

https://golang.org/ref/spec#Type_assertions

對於單個返回值斷言:當它失敗時程序會發生混亂。

對於兩個返回值斷言:當它失敗時,第二個參數設置為false並且程序不會發生混亂。

類型斷言是x。(T)表示法,其中x是接口類型,T是類型。 另外,存儲在x中的實際值是T類型,T必須滿足x的接口類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM