Golang 學習——interface 介面學習(二)

相守之路發表於2020-05-07

Golang介面斷言學習

Golang中,空介面 interface{}沒有定義任何函式,因此Golang 中所有型別都實現了空介面。當一個函式的形參是interface{},那麼在函式中,需要對形參進行斷言,從而得到它的真實型別。

在學習介面斷言之前,先了解一下型別斷言,其實介面斷言也是在判斷型別。

型別斷言,通過它可以做到以下幾件事情:

  1. 檢查 i 是否為 nil
  2. 檢查 i 儲存的值是否為某個型別

通常有兩種方式:
第一種:

t := i.(T)

這個表示式可以斷言一個介面物件i裡不是 nil,並且介面物件i儲存的值的型別是 T,如果斷言成功,就會返回值給t,如果斷言失敗,就會觸發 panic

t := i.(T) 常用於 switch 結構。

第二種:

t, ok:= i.(T)

這個表示式也是可以斷言一個介面物件t裡不是 nil,並且介面物件t儲存的值的型別是 T;

如果斷言成功,就會返回其型別給t,並且此時 ok 的值 為 true,表示斷言成功。

如果介面值的型別,並不是我們所斷言的 T,就會斷言失敗,但和第一種表示式不同的事,這個不會觸發 panic,而是將 ok 的值設為 false ,表示斷言失敗,此時tT 的零值。

t, ok:= i.(T) 常用於 if else 結構。

1.if else結構 介面斷言

t, ok := i.(T) 斷言在上一小節已經介紹過了,本小節,我們通過實戰加深下理解。

我們先建立一個Shape形狀介面,兩個結構體。

// 定義介面
type Shape interface {
    perimeter() float64 // 返回形狀的周長
    area() float64      // 返回形狀的面積
}

// 定義結構體
type Circle struct {
    radius float64
}

type Triangle struct {
    a, b, c float64
}

其中,Shape介面有兩個方法,分別是求形狀的周長和麵積。

兩個結構體分別定義了自己獨有的屬性:

  • Circle(圓),定義了半徑
  • Triangle(三角形),定義了三條邊

接下來,我們實現Shape介面中的方法:

// 圓結構體 實現介面方法
func (c Circle) perimeter() float64 {
    return c.radius * math.Pi * 2
}

func (c Circle) area() float64 {
    return math.Pow(c.radius, 2) * math.Pi
}

// 三角形結構體 實現介面方法
func (t Triangle) perimeter() float64 {
    return t.a + t.b + t.c
}
func (t Triangle) area() float64 {
    p := t.perimeter() / 2
    return math.Sqrt(p * (p - t.a) * (p - t.b) * (p - t.c))
}

其中三角形的面積計算使用了 海倫公式

接下來我們封裝一個介面斷言函式:

// 定義介面斷言函式
func getInterfaceType(s Shape) {
    if ins, ok := s.(Triangle); ok {
        fmt.Println("是三角形,三邊分別為:", ins.a, ins.b, ins.c)
    } else if ins, ok := s.(Circle); ok {
        fmt.Println("是圓形,半徑為;", ins.radius)
    } else if ins, ok := s.(*Circle); ok {
        fmt.Printf("是圓形結構體指標,型別為:%T,儲存的地址為:%p,指標自身的地址為:%p\n", ins, &ins, ins)
    } else {
        fmt.Println("無法判斷型別...")
    }
}

該函式中不僅判斷了值傳遞的型別,也判斷了引用傳遞(指標型別)的型別。因為Struct是值型別,所以我們加入引用型別,使練習更嚴謹一點。

接下來開始初始化結構體:

// 初始化一個圓結構體
c1 := Circle{radius: 10}
fmt.Println("==================圓結構體:==================")
fmt.Println("圓的周長為:", c1.perimeter())
fmt.Println("圓的面積為:", c1.area())

// 初始化一個三角形結構體
t1 := Triangle{
    a: 3,
    b: 4,
    c: 5,
}
fmt.Println("================三角形結構體:=================")
fmt.Println("三角形的周長為:", t1.perimeter())
fmt.Println("三角形的面積為:", t1.area())

// 初始化一個圓形結構體指標
var c2 *Circle = &Circle{radius: 5}
fmt.Println("================圓形結構體指標:===============")
fmt.Println("圓的周長為:", c2.perimeter())
fmt.Println("圓的面積為:", c2.area())

輸出:

==================圓結構體:==================
圓的周長為: 62.83185307179586
圓的面積為: 314.1592653589793
================三角形結構體:=================
三角形的周長為: 12
三角形的面積為: 6
================圓形結構體指標:===============
圓的周長為: 31.41592653589793
圓的面積為: 78.53981633974483

可以看到,以上結構體都實現了Shape介面, 接下來開始進行介面斷言:

fmt.Println("==============t, ok:= i.(T) 開始介面斷言====================")
getInterfaceType(c1) // 判斷該介面是否為 圓形結構體型別
getInterfaceType(t1) // 判斷該介面是否為 圓形結構體型別
getInterfaceType(c2) // 判斷該介面是否為 圓形結構體指標型別

輸出:

==============t, ok:= i.(T) 開始介面斷言===================
是圓形,半徑為; 10
是三角形,三邊分別為: 3 4 5
是圓形結構體指標,型別為:*main.Circle,儲存的地址為:0xc000006030,指標自身的地
址為:0xc0000140e0

可以看到,我們的介面斷言奏效了,並且輸出了對應邏輯的結果。

2.switch結構 介面斷言

斷言其實還有另一種形式,就是用在利用switch語句判斷介面的型別。

每一個case會被順序地考慮。當命中一個case 時,就會執行 case 中的語句。

因此 case 語句的順序是很重要的,因為很有可能會有多個 case匹配的情況。

我們再封裝一個 switch邏輯的介面斷言函式,邏輯和之前的一模一樣,只是條件語句換成了 switch....case

// 定義介面斷言函式,使用 switch
func getInterfaceTypeSwitch(s Shape) {
    switch ins := s.(type) { // 首字母小寫的 type
    case Circle:
        fmt.Println("是圓形,半徑為;", ins.radius)
    case Triangle:
        fmt.Println("是三角形,三邊分別為:", ins.a, ins.b, ins.c)
    case *Circle:
        fmt.Printf("是圓形結構體指標,型別為:%T,儲存的地址為:%p,指標自身的地址為:%p\n", ins, &ins, ins)
    default:
        fmt.Println("無法判斷型別...")
    }
}

接下來測試封裝的函式:

fmt.Println("==============t := i.(type) 開始介面斷言====================")
getInterfaceTypeSwitch(c1) // 判斷該介面是否為 圓形結構體型別
getInterfaceTypeSwitch(t1) // 判斷該介面是否為 圓形結構體型別
getInterfaceTypeSwitch(c2) // 判斷該介面是否為 圓形結構體指標型別

輸出:

==============t := i.(type) 開始介面斷言====================
是圓形,半徑為; 10
是三角形,三邊分別為: 3 4 5
是圓形結構體指標,型別為:*main.Circle,儲存的地址為:0xc000006038,指標自身的地
址為:0xc0000140e0

可以看到,switch斷言的邏輯也正常輸出了。

總結一下,今天主要記錄了介面如何斷言的,通常有兩種方式:

  1. 方式一:t, ok:= i.(T)
  • 斷言成功,就會返回其型別給t,並且此時 ok 的值 為 true,表示斷言成功
  • 斷言失敗,okfalsetT的零值
  • 通常用於if else結構
  1. 方式二:t := i.(T)
  • 斷言一個介面物件i裡不是 nil,並且介面物件i儲存的值的型別是 T
  • 如果斷言成功,就會返回值給 t,如果斷言失敗,就會觸發 panic
  • 通常用於switch結構
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章