2016-08-07 107 views
3

我在閱讀關於類型斷言x.(T) in The Go Programming Language並且不理解它們。在Go中解釋類型斷言

據我所知,有不同的方案:

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

這是我不明白:

  • 我爲什麼要用它們?
  • 他們究竟返回什麼?

我也google了一下這個話題,但還是不明白。

回答

4

在一個行:

x.(T)斷言x不是nil,並且存儲在x值爲T類型。

爲什麼我使用它們:

  • 檢查x爲零或不
  • 檢查它轉換(斷言)是另一種類型的
  • 轉換(斷言)爲另一種類型

他們究竟返回什麼:

  • t := x.(T) => t是T類型,如果x是零它恐慌。

  • t,ok := x.(T) =>如果x爲零或不的類型T =>okfalse否則oktruetT類型。


想象一下,你需要計算的4種不同形狀的區域:圓形,方形,長方形和三角形。

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: 所以,你可以用稱爲Area()新方法,像這樣定義一個新類型

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 
} 

現在是時候從上面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,所以如果它是成功的oktrue否則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 
} 

另見:

Type assertions

對於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。否則它是錯誤的,並且v的值是類型T的零值。在這種情況下不發生運行時恐慌

編輯:
問:什麼是斷言x.(T)返回時T是interface{},而不是一個具體的類型?
答案:

斷言x是不是nil和存儲在x中的值的類型T.

例如的這種恐慌(編譯:成功,運行:panic: interface conversion: interface is nil, not interface {}):

package main 

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

而這個工作(運行:OK):

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 
} 
+0

很好的解釋!我現在明白它好多了。還有一件事,如果我可能要求你補充一下:當T是一個接口而不是一個具體類型時,斷言返回什麼? – user1283776

+0

@ user1283776:'var _ = i。(interface {})'如果'i'是零它恐慌。但是在'b,ok:= i。(interface {})'中,如果'i'爲零,那麼'ok'爲false,否則'ok'爲真,'b'爲類型'interface {}',並且看到本帖子末尾的示例代碼。 –

1

常見的用例:檢查是否返回的錯誤是一個類型T

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

對於一個返回值的斷言:在失敗時的程序恐慌。

對於兩個返回值斷言:當它失敗時,第二個參數設置爲false,程序不會出現恐慌。