介面類型探測:類型斷言 介面實例中存儲了實現介面的類型實例,類型的實例有兩種:值類型實例和指針類型實例。在程式運行過程中,介面實例存儲的實例類型可能會動態改變。例如: 所以,需要一種探測介面實例所存儲的是值類型還是指針類型。 探測的方法是: 和`ins.( Type)`。它們有兩個返回值,第二個返回 ...
介面類型探測:類型斷言
介面實例中存儲了實現介面的類型實例,類型的實例有兩種:值類型實例和指針類型實例。在程式運行過程中,介面實例存儲的實例類型可能會動態改變。例如:
// ins是介面實例
var ins Shaper
// ins存儲值類型的實例
ins = c1
// 一段時間後...
...
// ins存儲指針類型的實例,存儲的類型發生改變
ins = c2
// 一段時間後...
// ins可能存儲另一個類型實例
ins = s1
所以,需要一種探測介面實例所存儲的是值類型還是指針類型。
探測的方法是:ins.(Type)
和ins.(*Type)
。它們有兩個返回值,第二個返回值是ok返回值,布爾類型,第一個返回值是探測出的類型。也可以只有一個返回值:探測出的類型。
// 如果ins保存的是值類型的Type,則輸出
if t, ok := ins.(Type); ok {
fmt.Printf("%T\n", v)
}
// 如果ins保存的是指針類型的*Type,則輸出
if t, ok := ins.(*Type); ok {
fmt.Printf("%T\n", v)
}
// 一個返回值的探測
t := ins.(Type)
t := ins.(*Type)
以下是一個例子:
package main
import "fmt"
// Shaper 介面類型
type Shaper interface {
Area() float64
}
// Square struct類型
type Square struct {
length float64
}
// Square類型實現Shaper中的方法Area()
func (s Square) Area() float64 {
return s.length * s.length
}
func main() {
var ins1, ins2, Shaper
// 指針類型的實例
s1 := new(Square)
s1.length = 3.0
ins1 = s1
if v, ok := ins1.(*Square); ok {
fmt.Printf("ins1: %T\n", v)
}
// 值類型的實例
s2 := Square{4.0}
ins2 = s2
if v, ok := ins2.(Square); ok {
fmt.Printf("ins2: %T\n", v)
}
}
上面兩個Printf都會輸出,因為它們的類型判斷都返回true。如果將ins2.(Square)
改為ins2.(*Square)
,第二個Printf將不會輸出,因為ins2它保存的是值類型的實例。
特別需要註意的是,ins必須明確是介面實例。例如,以下前兩種聲明是有效的,第三種推斷類型是錯誤的,因為它可能是介面實例,也可能是類型的實例副本。
var ins Shaper // 正確
ins := Shaper(s1) // 正確
ins := s1 // 錯誤
當ins不能確定是介面實例時,用它來進行測試,例如ins.(Square)
將會報錯:
invalid type assertion:ins.(Square) (non-interface type (type of ins) on left)
它說明瞭左邊的ins是非介面類型(non-interface type)。
type Switch結構
switch流程式控制制結構還可以用來探測介面實例保存的類型。這種結構稱為type-switch。
用法如下:
switch v := ins.(type) {
case *Square:
fmt.Printf("Type Square %T\n", v)
case *Circle:
fmt.Printf("Type Circle %T\n", v)
case nil:
fmt.Println("nil value: nothing to check?")
default:
fmt.Printf("Unexpected type %T", v)
}
其中ins.(type)
中的小寫type是固定的詞語。
以下是一個使用示例:
package main
import (
"fmt"
)
// Shaper 介面類型
type Shaper interface {
Area() float64
}
// Circle struct類型
type Circle struct {
radius float64
}
// Circle類型實現Shaper中的方法Area()
func (c *Circle) Area() float64 {
return 3.14 * c.radius * c.radius
}
// Square struct類型
type Square struct {
length float64
}
// Square類型實現Shaper中的方法Area()
func (s Square) Area() float64 {
return s.length * s.length
}
func main() {
s1 := &Square{3.3}
whichType(s1)
s2 := Square{3.4}
whichType(s2)
c1 := new(Circle)
c1.radius = 2.3
whichType(c1)
}
func whichType(n Shaper) {
switch v := n.(type) {
case *Square:
fmt.Printf("Type Square %T\n", v)
case Square:
fmt.Printf("Type Square %T\n", v)
case *Circle:
fmt.Printf("Type Circle %T\n", v)
case nil:
fmt.Println("nil value: nothing to check?")
default:
fmt.Printf("Unexpected type %T", v)
}
}
上面的type-switch中,之所以沒有加上case Circle
,是因為Circle只實現了指針類型的receiver,根據Method Set對介面的實現規則,只有指針類型的Circle示例才算是實現了介面Shaper,所以將值類型的示例case Circle
放進type-switch是錯誤的。