溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Go語言6-接口、反射

發(fā)布時間:2020-07-18 05:11:14 來源:網(wǎng)絡(luò) 閱讀:655 作者:騎士救兵 欄目:編程語言

接口

接著上次的繼續(xù)講接口,先回顧一下接口的用法:

package main

import "fmt"

// 定義接口
type Car interface {
    GetName() string
    Run()
}

// 定義結(jié)構(gòu)體
type Tesla struct {
    Name string
}

// 實(shí)現(xiàn)接口的GetName()方法
func (t *Tesla) GetName() string {
    return t.Name
}

// 實(shí)現(xiàn)接口的Run()方法
func (t *Tesla) Run() {
    fmt.Printf("%s is running\n", t.Name)
}

func main() {
    var c Car
    var t Tesla = Tesla{"Tesla Model S"}
    c = &t  // 上面是用指針*Tesla實(shí)現(xiàn)了接口的方法,這里要傳地址
    /*  或者在定義的時候,就定義結(jié)構(gòu)體指針
    var t *Tesla = &Tesla{"Tesla Model X"}
    c = t
    */
    fmt.Println(c.GetName())
    c.Run()
}

強(qiáng)調(diào)一下:interface 類型默認(rèn)是一個指針

空接口

沒有定義任何方法的接口,就是空接口:

type Empty interface{}  // 定義了一個接口類型 Empty,里面沒有任何方法

var e1 Empty  // e1 就是一個空接口
var e2 interface{}  // e2 也是空接口,這里跳過了接口類型的定義,在定義接口的同時把接口類型一起做了

由于空接口里沒有定義任何方法,任何類型都實(shí)現(xiàn)了空接口。也就是空接口可以被任何類型實(shí)現(xiàn),空接口能夠容納任何類型。

package main

import "fmt"

func main(){
    var e interface{}  // 定義一個空接口
    var n int
    e = n  // n可以給e賦值,因?yàn)閚實(shí)現(xiàn)了e。這樣接口就能存儲它具體的實(shí)現(xiàn)類
    //n = e  // 反過來就不行,
    fmt.Printf("%T %T\n", n, e)  // 通過接口也能獲取到它的實(shí)現(xiàn)類
}

之前一直使用的 fmt.Println() ,什么類型都可以往里傳。這個函數(shù)接收的參數(shù)是這樣的:

func(a ...interface{}) (n int, err error)

這里單看參數(shù)類型,就是空接口,任何類型都實(shí)現(xiàn)了空接口,所以任何類型都能作為參數(shù)。
類型轉(zhuǎn)換
空接口也是個類型,類型轉(zhuǎn)換的用法是一樣的,不要遇到了大括號就看不懂了:

var i int  // 定義一個int類型
j := int32(i)  // 轉(zhuǎn)成int32
k := interface{}(i)  // 轉(zhuǎn)成空接口類型

對自定義結(jié)構(gòu)體排序

排序使用 sort 包。包里提供了 Sort 方法可以對接口進(jìn)行排序。

func Sort(data Interface)

Sort 對 data 進(jìn)行排序。它調(diào)用一次 data.Len 來決定排序的長度 n,調(diào)用 data.Less 和 data.Swap 的開銷為 O(n*log(n))。此排序?yàn)椴环€(wěn)定排序。
這里是對接口進(jìn)行排序,所以傳入的 data 參數(shù)需要實(shí)現(xiàn)接口里的方法,接口的定義如下:

type Interface interface {
    // Len is the number of elements in the collection.
    // Len 為集合內(nèi)元素的總數(shù)
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    //
    // Less 返回索引為 i 的元素是否應(yīng)排在索引為 j 的元素之前。
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    // Swap 交換索引為 i 和 j 的元素
    Swap(i, j int)
}

所以要對你的自定義結(jié)構(gòu)體進(jìn)行排序,首先定義一個該結(jié)構(gòu)體類型的切片類型,然后實(shí)現(xiàn)上面的3個方法。之后就可以插入數(shù)據(jù)然后進(jìn)行排序了:

package main

import (
    "fmt"
    "sort"
)

// 自定義的結(jié)構(gòu)體
type Animal struct {
    Type   string
    Weitht int
}

// 新定義一個切片的類型,下面對這個類型實(shí)現(xiàn)interface要求的3個方法
// 直接用 []Animal Go不認(rèn),這里應(yīng)該是起了個別名
type AnimalSlice []Animal

// 對自定義的切片類型實(shí)現(xiàn)Sort的接口要求的3個方法
func (a AnimalSlice) Len() int {
    return len(a)
}

func (a AnimalSlice) Less(i, j int) bool {
    return a[i].Weitht < a[j].Weitht
}

func (a AnimalSlice) Swap(i, j int) {
    a[i], a[j] = a[j], a[i]
}

func main() {
    var tiger Animal = Animal{"Tiger", 200}
    var dog Animal = Animal{"Dog", 20}
    var cat Animal = Animal{"Cat", 15}
    var elephant Animal = Animal{"Elephant", 4000}
    // 這里的切片要用自定義的類型,別名被認(rèn)為是兩個不同的類型,只有這個實(shí)現(xiàn)了接口的方法
    var data AnimalSlice
    data = append(data, tiger)
    data = append(data, dog)
    data = append(data, cat)
    data = append(data, elephant)
    fmt.Println(data)
    sort.Sort(data)
    fmt.Println(data)
}

接口嵌套

一個接口可以嵌套另外的接口:

type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}

type Lock interface {
    Lock()
    Unlock
}

type File interface {
    ReadWrite
    Lock
    Close
}

嵌套的用法類似結(jié)構(gòu)體的繼承。這樣如果已經(jīng)有了一些接口,只要把這些接口組合一下,就又產(chǎn)生了一些新的接口了,不用去重復(fù)定義。
再來寫個例子,主要是熟悉對接口編程的思路,順便用到了接口的嵌套:

package main

import "fmt"

// 定義一個接口
type Reader interface {
    Read()
}

// 再定義一個接口
type Writer interface {
    Write(s string)
}

// 定義第三個接口,嵌套上面的兩個接口
type ReadWriter interface {
    Reader
    Writer
}

// 定義一個結(jié)構(gòu)體
type file struct {
    content string
}

func (f *file) Read(){
    fmt.Println(f.content)
}

func (f *file) Write(s string){
    f.content = s
    fmt.Println("寫入數(shù)據(jù):", s)
}

// 這個函數(shù)是對接口進(jìn)行操作,上面的file類型實(shí)現(xiàn)了接口的所有方法
func CheckChange(rw ReadWriter, s string) {
    rw.Read()
    rw.Write(s)
    rw.Read()
}

func main() {
    var f file = file{"Hello"}
    CheckChange(&f, "How are you")
}

上面寫的 CheckChange() 方法,只有是實(shí)現(xiàn)了 ReadWriter 這個接口的任何類型,都可以用這個函數(shù)來調(diào)用。

類型斷言

類型斷言,由于接口是一般類型,不知道具體的類型。
之前的例子里的函數(shù),定義的入?yún)⑹墙涌凇6鴮?shí)際傳入的是某個實(shí)現(xiàn)了接口類型的具體類型。比如上面的例子 CheckChange() 方法接收的參數(shù)主要是實(shí)現(xiàn)了 ReadWriter 接口的任何類型都可以??梢允抢永锏淖远x類型 file。也可以是別的類型比如再自定義一個 message。這樣在函數(shù)接收參數(shù)后,是不知道這個參數(shù)的具體類型的。有些場景,你需要知道這個接口指向的具體類型是什么。
可以把接口類型轉(zhuǎn)成具體類型,如果要轉(zhuǎn)成具體類型,可以采用以下方法進(jìn)行轉(zhuǎn)換:

package main

import "fmt"

func main() {
    var i int = 10  // 這個是int
    var j interface{}  // 這個是空接口
    fmt.Printf("%T %v\n", j, j)  // 還沒給j賦值,現(xiàn)在j只是一個空指針,默認(rèn)類型和默認(rèn)值都是nil
    j = i  // 任何類型都可以給空接口賦值,如果i是參數(shù)傳入的,現(xiàn)在并不知道i的類型
    fmt.Printf("%T %v\n", j, j)  // 可以打印查看現(xiàn)在的j的類型和值都是和i一樣的,但是代碼層面還是不知道具體類型
    res := j.(int)  // 轉(zhuǎn)成int類型,如果不是int類型會報錯
    //res := j.(int32)  // 轉(zhuǎn)成int32,由于類型不對,會報錯
    fmt.Printf("%T %v\n", res, res)
}

上面在 res := j.(int) 這句做類型轉(zhuǎn)換之前,打印 j 的類型的時候已經(jīng)看到類型是 int 了,但是其實(shí) j 的類型在代碼層面還不知道。需要執(zhí)行這句類型轉(zhuǎn)換把類型轉(zhuǎn)成 int 。下面的函數(shù)接收空接口,但是內(nèi)部要做加法,只有將參數(shù)轉(zhuǎn)成數(shù)值類型后,才能做加法:

func add(a interface{}){
    b := a.(int)  // 只有做了類型轉(zhuǎn)換,才能做下面的加法
    b++
    c := a
    fmt.Printf("%T %v\n", c, c)  // 雖然能打印出類型,但是代碼層面這個的類型還是interface{}
    //c++  // 這句還不能執(zhí)行,現(xiàn)在c的類型是interface{},只有數(shù)值類型能做加法
}

上面是不帶檢查的,如果類型轉(zhuǎn)換不成功,會報錯。下面是帶檢查的類型斷言:

package main

import "fmt"

type Num struct {
    n int
}

func main() {
    var i Num = Num{1}
    var j interface{}
    j = i
    res, ok := j.(int)  // 帶檢查的類型斷言
    fmt.Println(res, ok)  // ok是false,類型不對,res的值就是轉(zhuǎn)換類型的默認(rèn)值
    var k interface{}
    k = i
    res2, ok := k.(Num)  // 這次類型是對的
    fmt.Println(res2, ok)  // ok是true
}

判斷類型

除了類型斷言,還有這個方法可以判斷類型。
下面的函數(shù)可以判斷傳入?yún)?shù)的類型:

package main

import "fmt"

func classifier(items ...interface{}) {
    for i, v := range items {
        switch v.(type) {
        case bool:
            fmt.Println("bool", i)
        case float64:
            fmt.Println("float64", i)
        case int:
            fmt.Println("int", i)
        case nil:
            fmt.Println("nil", i)
        case string:
            fmt.Println("string", i)
        default:
            fmt.Println("unknow", i)
        }
    }
}

func main() {
    classifier(1, "", nil, 1.234, true, int32(5))
}

/* 執(zhí)行結(jié)果
PS H:\Go\src\go_dev\day6\interface\classifier> go run main.go
int 0
string 1
nil 2
float64 3
bool 4
unknow 5
PS H:\Go\src\go_dev\day6\interface\classifier>
*/

這里用到了 v.(type) ,這個必須與 switch case 聯(lián)合使用,如果寫在 switch 外面,編譯器會報錯。

判斷是否實(shí)現(xiàn)了指定接口

語法如下:

v, ok := interface{}(實(shí)例).(接口名)

先要把類型轉(zhuǎn)成空接口,然后再判斷是否實(shí)現(xiàn)了指定的接口。
示例:

package main

import "fmt"

// 定義一個結(jié)構(gòu)體
type Example struct{
    Name string
}

// 這是一個接口
type IF1 interface{
    Hello()
}

// 這是另一個接口
type IF2 interface{
    Hi()
}

// 實(shí)現(xiàn)了接口 IF1 的方法
func (e Example) Hello(){
    fmt.Println("Hello")
}

func main(){
    var e Example = Example{"TEST"}  // 這里可以不做初始化的,不初始化也是有默認(rèn)值的,srting型就是空
    v, ok := interface{}(e).(IF1)
    fmt.Println(v, ok)
    v2, ok := interface{}(e).(IF2)
    fmt.Println(v2, ok)
}

/* 執(zhí)行結(jié)果
PS H:\Go\src\go_dev\day6\interface\is_if> go run main.go
{TEST} true
<nil> false
PS H:\Go\src\go_dev\day6\interface\is_if>
*/

接口示例

實(shí)現(xiàn)一個通用的鏈表類
重點(diǎn)要實(shí)現(xiàn)尾插法,頭插法的當(dāng)前節(jié)點(diǎn)不用移動,始終是頭節(jié)點(diǎn)就行了。而尾插法要有一個當(dāng)前節(jié)點(diǎn)的指針始終指向最后的一個節(jié)點(diǎn)。示例:

// go_dev\day6\interface\link\link\link.go
package link

import (
    "fmt"
)

type Link struct{
    Data interface{}  // 數(shù)據(jù)是空接口,所以是通用類型
    Next *Link
}

// 頭插法,p需要傳指針,因?yàn)榉椒ɡ镄枰淖僷的值
// 但是p本身也是個指針,所以接收的類型是指針的指針
func (l *Link) AddNodeHead(data interface{}, p **Link){
    var node Link
    node.Data = data
    node.Next = (*p).Next
    (*p).Next = &node
}

// 尾插法
func (l *Link) AddNodeTail(data interface{}, p **Link){
    var node Link
    node.Data = data
    (*p).Next = &node
    (*p) = &node
}

// 遍歷鏈表的方法,打印當(dāng)前節(jié)點(diǎn)以及之后的所有的節(jié)點(diǎn)
func (l *Link) Trans(){
    for l != nil {
        fmt.Println(*l)
        l = l.Next
    }
}

// go_dev\day6\interface\link\main\main.go
package main

import (
    "../link"
)

func main(){
    var intLink link.Link  // 別名,后面都用intLink
    head := intLink  // head是頭節(jié)點(diǎn)
    p := &head  // p是指向當(dāng)前節(jié)點(diǎn)的指針,注意結(jié)構(gòu)體是值類型
    // 插入節(jié)點(diǎn)
    for i := 0; i < 10; i++ {
        node := intLink
        node.Data = i
        // 插入節(jié)點(diǎn)的方法,需改改變p本真的值,這里就要把p的地址傳進(jìn)去
        // 由于p本身已經(jīng)是個指針了,再傳指針的地址,那個變量就是指針的指針
        //intLink.AddNodeHead(node, &p)  // 頭插法
        intLink.AddNodeTail(node, &p)  // 尾插法
    }
    head.Trans()  // 從頭節(jié)點(diǎn)遍歷鏈表
}

這個例子用了指針的指針。因?yàn)榻Y(jié)構(gòu)體是值類型,指向當(dāng)前節(jié)點(diǎn)的變量p需要是一個指針類型。然而在添加節(jié)點(diǎn)的方法里(主要是尾插法),需要改變p的值,將p重新指向新插入的節(jié)點(diǎn)。這就要求必須把p的地址傳進(jìn)來,這樣就是指針的指針了。
其實(shí)也可以不用那么做,不在方法里改變p的值,而是給方法添加一個返回值,返回最新的當(dāng)前節(jié)點(diǎn)。這樣就需要在調(diào)用方法的時候獲取返回值然后賦值給p,就是在方法外改變p的值,這樣就可以傳p的副本給方法處理了。

實(shí)現(xiàn)一個負(fù)載均衡的調(diào)度算法,支持隨機(jī)、輪訓(xùn)等算法

// go_dev\day6\interface\balance\balancd\balance.go
// 定義接口、結(jié)構(gòu)體,以及對結(jié)構(gòu)體的封裝
package balance

import "fmt"

type Balancer interface {
    DoBalance([]*Instance) (*Instance, error)
}

// 封裝:里面的字段都是小寫,這樣外部不可見,就無法查看也無法修改,甚至無法創(chuàng)建
// 在對結(jié)構(gòu)體做什么樣的操作,就再寫方法來實(shí)現(xiàn)
// 這樣就可以把這個結(jié)構(gòu)體封裝好,只能用提供的方法進(jìn)行有限的操作
type Instance struct {
    host string
    port int
}

// 結(jié)構(gòu)體里的字段都是小寫,外部不可見,外部調(diào)用構(gòu)造函數(shù)創(chuàng)建結(jié)構(gòu)體
func NewInstance(host string, port int) *Instance {
    return &Instance{host, port}
}

// 還是因?yàn)榉庋b,這里再提供方法可以查詢
func (i *Instance) GetHost() string{
    return i.host
}

func (i *Instance) GetPort() int{
    return i.port
}

func (i *Instance) String() string{
    return fmt.Sprintf("%v:%v", i.host, i.port)
}

// go_dev\day6\interface\balance\balancd\random.go
// 隨機(jī)算法
package balance

import (
    "errors"
    "math/rand"
    "time"
)

func init() {
    rand.Seed(time.Now().UnixNano())
}

// 雖然是個空結(jié)構(gòu)體,但是需要一個結(jié)構(gòu)體類型,然后去實(shí)現(xiàn)Balancer接口里的方法
type RandomBalance struct{
}

func (b *RandomBalance) DoBalance(objs []*Instance) (obj *Instance, err error) {
    if len(objs) == 0 {
        err = errors.New("沒有傳入任何實(shí)例")
        return
    }
    l := len(objs)
    i := rand.Intn(l)
    obj = objs[i]
    return
}

// go_dev\day6\interface\balance\balancd\round_robin.go
// 輪訓(xùn)算法
package balance

import (
    "errors"
)

type RoundRobinBalance struct{
    Index int
}

func (b *RoundRobinBalance) DoBalance(objs []*Instance) (obj *Instance, err error) {
    if len(objs) == 0 {
        err = errors.New("沒有傳入任何實(shí)例")
        return
    }
    l := len(objs)
    if b.Index >= l {
        b.Index = 0
    }
    obj = objs[b.Index]
    b.Index = (b.Index + 1) % l
    return
}

// go_dev\day6\interface\balance\main\main.go
// 使用和測試上面寫的balance包
package main

import (
    "../balance"
    "fmt"
    "os"
    "strconv"
)

func main(){
    var objs []*balance.Instance
    for i := 0; i < 5; i++ {
        host := "Host" + strconv.Itoa(i)
        port := 80 + i
        obj := balance.NewInstance(host, port)
        objs = append(objs, obj)
    }

    var balancer balance.Balancer
    var arg = "round"
    if len(os.Args) > 1 {
        arg = os.Args[1]
    }
    if arg == "random" {
        balancer = &balance.RandomBalance{}
    } else if arg == "round" {
        balancer = &balance.RoundRobinBalance{}
    } else {
        arg = "random"
        // 不同的參數(shù)對應(yīng)不同的負(fù)載均衡算法,生成不同的實(shí)例
        // 直接再通過實(shí)例調(diào)用里面的方法,是不需要用到接口的
        // 在這里,要用balancer這個變量來接收不同的結(jié)構(gòu)體類型,就需要用到接口了
        // 只要實(shí)現(xiàn)了接口的里DoBalance()方法,就可以賦值給接口,之后就用接口統(tǒng)一調(diào)用這個方法
        balancer = &balance.RandomBalance{}
    }
    fmt.Println("負(fù)載均衡算法:", arg)

    // 調(diào)用10次,檢查負(fù)載均衡的效果
    for i := 0; i < 10; i++{
        obj, err := balancer.DoBalance(objs)  // 通過接口統(tǒng)一調(diào)用方法
        if err != nil {
            fmt.Println("異常:", err)
            continue
        }
        fmt.Println(obj)
    }
}

反射

反射,可以在運(yùn)行時動態(tài)的獲取到變量的相關(guān)信息。需要 reflect 包:

import "reflect"

基本用法

主要是下面這2個函數(shù):

  • func TypeOf(i interface{}) Type : 獲取變量的類型,返回 reflect.Type 類型
  • func ValueOf(i interface{}) Value : 獲取變量的值,返回 reflect.Value 類型
package main

import (
    "fmt"
    "reflect"
)

func test(a interface{}){
    t := reflect.TypeOf(a)
    fmt.Println(t)
    v := reflect.ValueOf(a)
    fmt.Println(v)
}

func main(){
    n := 100
    test(n)
}

/* 執(zhí)行結(jié)果
PS H:\Go\src\go_dev\day6\reflect\beginning> go run main.go
int
100
PS H:\Go\src\go_dev\day6\reflect\beginning>
*/

在 reflect.Value 里提供了很多方法。大多數(shù)情況下,都是要先獲取到 reflect.Value 類型,然后再調(diào)用對應(yīng)的方法來實(shí)現(xiàn)。

獲取類別(kind)

類型(type)和類別(kind),原生的類型兩個的名字應(yīng)該是一樣了。不過自定義類型比如結(jié)構(gòu)體,type就是我們自定義的名字,而kind就是struct。
要獲取kind,首先是用上面的方法獲取到 reflect.Value 類型,然后調(diào)用 Kind 方法,返回 reflect.Kind 類型:

func (v Value) Kind() Kind

具體用法:

package main

import (
    "fmt"
    "reflect"
)

type Example struct{}  // 自定義結(jié)構(gòu)體,看下類型和類別

func main(){
    a1 := 10
    t1 := reflect.TypeOf(a1)
    v1 := reflect.ValueOf(a1)
    k1 := v1.Kind()
    fmt.Println(t1, k1)  // 原生類型的類別看不出來
    a2 := Example{}
    t2 := reflect.TypeOf(a2)
    v2 := reflect.ValueOf(a2)
    k2 := v2.Kind()
    fmt.Println(t2, k2)  // 自定義結(jié)構(gòu)體的類型是自定義的名字,類別是struct
}

/* 執(zhí)行結(jié)果
PS H:\Go\src\go_dev\day6\reflect\kind> go run main.go
int int
main.Example struct
reflect.Kind string
*/

示例中我們最后看到的是打印輸出的效果。上面的兩個 Kind() 方法的返回值的類型是 reflect.Kind ,這是包里定義的常量。如果要進(jìn)行比較的話,這樣比較:

k1 == reflect.Struct
k2 == reflect.String

另外,返回的類型并不是字符串類型。返回的是包里定義的常量上面已經(jīng)講過了。如果要獲取類型的字符串名稱,可以用 reflect.Kind 類型的 String() 方法:

func (k Kind) String() string

轉(zhuǎn)成空接口

用法:

func (v Value) Interface() (i interface{})

示例:

package main

import (
    "fmt"
    "reflect"
)

type Student struct{
    Name string
    Age int
}

func main(){
    var s Student = Student{"Adam", 18}
    t := reflect.ValueOf(s)
    tif := t.Interface()  // 調(diào)用Interface()方法,返回空接口類型
    // 類型斷言,必須要用空接口調(diào)用
    if stu, ok := tif.(Student); ok{
        fmt.Printf("%T %v\n", stu, stu)
    }
}

獲取、設(shè)置變量

通過反射獲取變量的值:

func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Bool() bool
func (v Value) String() string

通過反射設(shè)置變量的值:

func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetBool(x bool)
func (v Value) SetString(x string)

如果要設(shè)置的是一個值類型,那么肯定是要傳地址的。但是傳地址之后,轉(zhuǎn)成了Value類型后就無法再用星號取到指針指向的內(nèi)容了。這里提供下面的 Elem() 方法。
取指針指向的值:

func (v Value) Elem() Value

示例:

package main

import (
    "fmt"
    "reflect"
)

func get(x interface{}){
    v := reflect.ValueOf(x)
    res := v.Int()
    fmt.Printf("%T %v\n", res, res)
}

func set(x interface{}){
    v := reflect.ValueOf(x)  // x如果是個指針,*x是可以用的
    // 但是通過ValueOf()方法獲得的v就不是指針了,沒法用*v
    // 所以有了下面的Elem()方法,效果就是我們想要的*v的效果
    v.Elem().SetInt(2)  // 先要用Elem獲取到指針指向的內(nèi)容,然后才能Set
}

func main(){
    var n int = 1
    get(n)
    set(&n)  // 這里肯定是要地址的
    get(n)
}

操作結(jié)構(gòu)體

返回結(jié)構(gòu)體里字段、方法的數(shù)量:

func (v Value) NumField() int
func (v Value) NumMethod() int

示例:

package main

import (
    "fmt"
    "reflect"
)

type Student struct{
    Name string
    Age int
    Score float32
}

func TestStruct(x interface{}){
    v := reflect.ValueOf(x)
    if k := v.Kind(); k != reflect.Struct {
        fmt.Println(v, "不是結(jié)構(gòu)體")
        return
    }
    fmt.Println(v, "是結(jié)構(gòu)體")

    numOfField := v.NumField()
    fmt.Println("結(jié)構(gòu)體里的字段數(shù)量:",numOfField)
    numOfMethod := v.NumMethod()
    fmt.Println("結(jié)構(gòu)體里的方法數(shù)量:",numOfMethod)
}

func main() {
    TestStruct(1)  // 傳個非結(jié)構(gòu)體測試一下效果
    var a Student = Student{"Adam", 17, 92.5}
    TestStruct(a)
}

獲取對應(yīng)的字段、方法
通過下標(biāo)獲?。?/p>

func (v Value) Field(i int) Value
func (v Value) Method(i int) Value

還有通過名字獲?。?/p>

func (v Value) FieldByName(name string) Value
func (v Value) FieldByNameFunc(match func(string) bool) Value
func (v Value) MethodByName(name string) Value

調(diào)用方法:
用上面的方法獲取到方法后,再 .Call(nil) 就可以執(zhí)行了。沒有參數(shù)的話傳 nil 就好了。Call只接收1個參數(shù),把方法需要的所有參數(shù)都轉(zhuǎn)成 Value 類型然后放在一個切片里傳給 Call 執(zhí)行。返回值也是切片,里面所有的值都是 Value 類型:

func (v Value) Call(in []Value) []Value

上面2句可以寫一行里,比如下面這樣,調(diào)用第一個方法,沒有參數(shù),不要返回值:

v.Method(0).Call(nil)

Type 接口的操作

這里用的是TypeOf() 方法,不要和上面的搞混了。返回值是 reflect.Type 類型,這是一個接口類型:

type Type interface {}

接口里的方法比較多,具體去官網(wǎng)看吧:https://go-zh.org/pkg/reflect/#Type

獲取字段的Tag對應(yīng)的內(nèi)容
json序列化是用Tag替換字段名的實(shí)現(xiàn),利用的也是這里的反射。
通過接口的 Field(i int) StructField 方法,傳入下標(biāo)獲取到的是一個 StructField 結(jié)構(gòu)體:

type StructField struct {
    // Name is the field name.
    // PkgPath is the package path that qualifies a lower case (unexported)
    // field name.  It is empty for upper case (exported) field names.
    // See http://golang.org/ref/spec#Uniqueness_of_identifiers
    Name    string
    PkgPath string

    Type      Type      // field type
    Tag       StructTag // field tag string
    Offset    uintptr   // offset within struct, in bytes
    Index     []int     // index sequence for Type.FieldByIndex
    Anonymous bool      // is an embedded field
}

結(jié)構(gòu)體里有一個字段是 Tag ,類型是 StructTag 。這是一個字符串類型的別名,不過里面實(shí)現(xiàn)了一些方法。調(diào)用 StructTag 的 Get 方法,傳入Tag的key,就能返回Tag里對應(yīng)的value:

func (tag StructTag) Get(key string) string

完整的代碼,抄官網(wǎng)的示例( https://go-zh.org/pkg/reflect/#example_StructTag ):

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type S struct {
        F string `species:"gopher" color:"blue"`
    }

    s := S{}
    st := reflect.TypeOf(s)  // 注意這里是TypeOf,返回值是 Type 接口
    field := st.Field(0)  // Type 接口里的方法,返回 StructField 結(jié)構(gòu)體。
    // StructField結(jié)構(gòu)體里面的Tag字段是 StructTag 一個 string 類型的別名
    // StructTag里實(shí)現(xiàn)了Get方法,下面就是調(diào)用該方法通過key獲取到value
    fmt.Println(field.Tag.Get("color"), field.Tag.Get("species"))
}

json序列化操作的時候,就是利用了反射的方法,獲取到tag里json這個key對應(yīng)的value,替換原本的字段名。

課后作業(yè)

實(shí)現(xiàn)一個圖書管理系統(tǒng)v2,增加以下功能:

  • 增加用戶登錄、注冊功能
  • 增加借書過期的圖書界面
  • 增加顯示熱門圖書的功能,被借次數(shù)最多的Top10
  • 增加查看某人的借書記錄的功能
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI