介面斷言
提到介面斷言,我們先回顧下怎么實作介面?
- 介面的實作者必須是一個具體型別
- 型別定義的方法和介面里方法名、引數、回傳值都必須一致
- 若介面有多個方法,那么要實作介面中的所有方法
對于空介面 interface{} ,因為它沒有定義任何的函式(方法),所以說Go中的所有型別都實作了空介面,
當一個函式的形參是 interface{} 時,意味著這個引數被自動的轉為interface{} 型別,在函式中,如果想得到引數的真實型別,就需要對形參進行斷言,
- 型別斷言就是將介面型別的值x,轉換成型別T,格式為:x.(T)
- 型別斷言x必須為介面型別
- T可以是非介面型別,若想斷言合法,則T必須實作x的介面
語法格式:
//非安全型別斷言
<目標型別的值> := <運算式>.( 目標型別 )
// 安全型別斷言
<目標型別的值>,<布爾引數> := <運算式>.( 目標型別 )
示例
package main
import "fmt"
func whoAmi(a interface{}) {
//1.不斷言
//程式報錯:cannot convert a (type interface{}) to type string: need type assertion
//fmt.Println(string(a))
//2.非安全型別斷言
//fmt.Println(a.(string)) //無塵
//3.安全型別斷言
value, ok := a.(string) //安全,斷言失敗,也不會panic,只是ok的值為false
if !ok {
fmt.Println("斷言失敗")
return
}
fmt.Println(value) //無塵
}
func main() {
str := "無塵"
whoAmi(str)
}
斷言還有一種形式,就是使用switch陳述句判斷介面的型別:
func whoAmi(a interface{}) {
switch a.(type) {
case bool:
fmt.Printf("boolean: %t\n", a) // a has type bool
case int:
fmt.Printf("integer: %d\n", a) // a has type int
case string:
fmt.Printf("string: %s\n", a) // a has type string
default:
fmt.Printf("unexpected type %T", a) // %T prints whatever type a has
}
}
反射
Go語言提供了一種機制,在運行時可以更新和檢查變數的值、呼叫變數的方法和變數支持的內在操作,但是在編譯時并不知道這些變數的具體型別,這種機制被稱為反射,
反射有何用
- 上面我們提到空介面,它能接收任何東西
- 但是怎么來判斷空介面變數存盤的是什么型別呢?上面介紹的型別斷言可以實作
- 如果想獲取存盤變數的型別資訊和值資訊就需要使用到反射
- 反射就是可以動態獲取變數型別資訊和值資訊的機制
reflect 包
反射是由reflect包來提供支持的,它提供兩種型別來訪問介面變數的內容,即Type 和 Value,
reflect包提供了兩個函式來獲取任意物件的Type 和 Value:
- func TypeOf(i interface{}) Type
- func ValueOf(i interface{}) Value
| 函式 | 作用 |
|---|---|
| reflect.TypeOf() | 獲取變數的型別資訊,如果為空則回傳nil |
| reflect.ValueOf() | 獲取資料的值,如果為空則回傳0 |
示例:
package main
import (
"fmt"
"reflect"
)
func main() {
var name string = "微客鳥窩"
// TypeOf會回傳變數的型別,比如int/float/struct/指標等
reflectType := reflect.TypeOf(name)
// valueOf回傳變數的的值,此處為"微客鳥窩"
reflectValue := reflect.ValueOf(name)
fmt.Println("type: ", reflectType) //type: string
fmt.Println("value: ", reflectValue) //value: 微客鳥窩
}
- 函式 TypeOf 的回傳值 reflect.Type 實際上是一個介面,定義了很多方法來獲取型別相關的資訊:
type Type interface {
// 所有的型別都可以呼叫下面這些函式
// 此型別的變數對齊后所占用的位元組數
Align() int
// 如果是 struct 的欄位,對齊后占用的位元組數
FieldAlign() int
// 回傳型別方法集里的第 `i` (傳入的引數)個方法
Method(int) Method
// 通過名稱獲取方法
MethodByName(string) (Method, bool)
// 獲取型別方法集里匯出的方法個數
NumMethod() int
// 型別名稱
Name() string
// 回傳型別所在的路徑,如:encoding/base64
PkgPath() string
// 回傳型別的大小,和 unsafe.Sizeof 功能類似
Size() uintptr
// 回傳型別的字串表示形式
String() string
// 回傳型別的型別值
Kind() Kind
// 型別是否實作了介面 u
Implements(u Type) bool
// 是否可以賦值給 u
AssignableTo(u Type) bool
// 是否可以型別轉換成 u
ConvertibleTo(u Type) bool
// 型別是否可以比較
Comparable() bool
// 下面這些函式只有特定型別可以呼叫
// 如:Key, Elem 兩個方法就只能是 Map 型別才能呼叫
// 型別所占據的位數
Bits() int
// 回傳通道的方向,只能是 chan 型別呼叫
ChanDir() ChanDir
// 回傳型別是否是可變引數,只能是 func 型別呼叫
// 比如 t 是型別 func(x int, y ... float64)
// 那么 t.IsVariadic() == true
IsVariadic() bool
// 回傳內部子元素型別,只能由型別 Array, Chan, Map, Ptr, or Slice 呼叫
Elem() Type
// 回傳結構體型別的第 i 個欄位,只能是結構體型別呼叫
// 如果 i 超過了總欄位數,就會 panic
Field(i int) StructField
// 回傳嵌套的結構體的欄位
FieldByIndex(index []int) StructField
// 通過欄位名稱獲取欄位
FieldByName(name string) (StructField, bool)
// FieldByNameFunc returns the struct field with a name
// 回傳名稱符合 func 函式的欄位
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 獲取函式型別的第 i 個引數的型別
In(i int) Type
// 回傳 map 的 key 型別,只能由型別 map 呼叫
Key() Type
// 回傳 Array 的長度,只能由型別 Array 呼叫
Len() int
// 回傳型別欄位的數量,只能由型別 Struct 呼叫
NumField() int
// 回傳函式型別的輸入引數個數
NumIn() int
// 回傳函式型別的回傳值個數
NumOut() int
// 回傳函式型別的第 i 個值的型別
Out(i int) Type
// 回傳型別結構體的相同部分
common() *rtype
// 回傳型別結構體的不同部分
uncommon() *uncommonType
}
- 函式 TypeOf 的回傳值 reflect.Value 是一個結構體型別,Value 結構體定義了很多方法,通過這些方法可以直接操作 Value 欄位 ptr 所指向的實際資料:
// 設定切片的 len 欄位,如果型別不是切片,就會panic
func (v Value) SetLen(n int)
// 設定切片的 cap 欄位
func (v Value) SetCap(n int)
// 設定字典的 kv
func (v Value) SetMapIndex(key, val Value)
// 回傳切片、字串、陣列的索引 i 處的值
func (v Value) Index(i int) Value
// 根據名稱獲取結構體的內部欄位值
func (v Value) FieldByName(name string) Value
// ……
struct反射示例:
package main
import (
"fmt"
"reflect"
)
type Address struct {
City string
}
type Person struct {
Name string
Age uint
Address // 匿名欄位
}
func (p Person) Hello(){
fmt.Println("我是無塵啊")
}
func main() {
//p := Person{Name:"無塵",Age:18,Address:Address{City:"北京"}} //map賦值
p := Person{"無塵",18,Address{"北京"}}
// 獲取目標物件
t := reflect.TypeOf(p)
fmt.Println("t:", t)
// .Name()可以獲取去這個型別的名稱
fmt.Println("型別的名稱:", t.Name())
// 獲取目標物件的值型別
v := reflect.ValueOf(p)
fmt.Println("v:", v)
// .NumField()獲取其包含的欄位的總數
for i := 0; i < t.NumField(); i++ {
// 從0開始獲取Person所包含的key
key := t.Field(i)
// interface方法來獲取key所對應的值
value := v.Field(i).Interface()
fmt.Printf("第%d個欄位是:%s:%v = %v \n", i+1, key.Name, key.Type, value)
}
// 取出這個City的詳情列印出來
fmt.Printf("%#v\n", t.FieldByIndex([]int{2, 0}))
// .NumMethod()來獲取Person里的方法
for i:=0;i<t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("第%d個方法是:%s:%v\n", i+1, m.Name, m.Type)
}
}
運行結果:
t: main.Person
型別的名稱: Person
v: {無塵 18 {北京}}
第1個欄位是:Name:string = 無塵
第2個欄位是:Age:uint = 18
第3個欄位是:Address:main.Address = {北京}
reflect.StructField{Name:"City", PkgPath:"", Type:(*reflect.rtype)(0x4cfe60), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
第1個方法是:Hello:func(main.Person)
- 通過反射修改內容
package main
import (
"reflect"
"fmt"
)
type Person struct {
Name string
Age int
}
func main() {
p := &Person{"無塵",18}
v := reflect.ValueOf(p)
// 修改值必須是指標型別
if v.Kind() != reflect.Ptr {
fmt.Println("非指標型別,不能進行修改")
return
}
// 獲取指標所指向的元素
v = v.Elem()
// 獲取目標key的Value的封裝
name := v.FieldByName("Name")
if name.Kind() == reflect.String {
name.SetString("wucs")
}
fmt.Printf("%#v \n", *p)
// 如果是整型的話
test := 666
testV := reflect.ValueOf(&test)
testV.Elem().SetInt(999)
fmt.Println(test)
}
運行結果:
main.Person{Name:"wucs", Age:18}
999
- 通過反射呼叫方法
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func (p Person) EchoName(name string){
fmt.Println("我的名字是:", name)
}
func main() {
p := Person{Name: "無塵",Age: 18}
v := reflect.ValueOf(p)
// 獲取方法控制權
// 官方解釋:回傳v的名為name的方法的已系結(到v的持有值的)狀態的函式形式的Value封裝
mv := v.MethodByName("EchoName")
// 拼湊引數
args := []reflect.Value{reflect.ValueOf("wucs")}
// 呼叫函式
mv.Call(args)
}
運行結果:
我的名字是: wucs
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/295957.html
標籤:Go
