天天看點

【go語言】

一、interface簡介

interface(接口)是golang最重要的特性之一,Interface類型可以定義一組方法,但是這些不需要實作。并且interface不能包含任何變量。

簡單的說:

  • interface是方法的集合
  • interface是一種類型,并且是指針類型
  • interface的更重要的作用在于多态實作

interface定義

type  接口名稱 interface {
method1 (參數清單) 傳回值清單
method2 (參數清單) 傳回值清單
...
}      

interface使用

  • 接口的使用不僅僅針對結構體,自定義類型、變量等等都可以實作接口。
  • 如果一個接口沒有任何方法,我們稱為空接口,由于空接口沒有方法,是以任何類型都實作了空接口。
  • 要實作一個接口,必須實作該接口裡面的所有方法。
【go語言】
package main

import "fmt"


//定義接口
type Skills interface {
    Running()
    Getname() string

}

type Student struct {
    Name string
    Age int
}


// 實作接口
func (p Student) Getname() string{   //實作Getname方法
    fmt.Println(p.Name )
    return p.Name
}

func (p Student) Running()  {   // 實作 Running方法
    fmt.Printf("%s running",p.Name)
}
func main()  {
    var skill Skills
    var stu1 Student
    stu1.Name = "wd"
    stu1.Age = 22
    skill = stu1
    skill.Running()  //調用接口
}

//wd running      
【go語言】

多态

上面提到了,go語言中interface是實作多态的一種形式,所謂多态,就是一種事物的多種形态,與python中類的多态是一緻的。

同一個interface,不同的類型實作,都可以進行調用,它們都按照統一接口進行操作。

在上面的示例中,我們增加一個Teacher結構體,同樣實作接口進行說明:

【go語言】
package main

import "fmt"

type Skills interface {
    Running()
    Getname() string

}

type Student struct {
    Name string
    Age int
}


type Teacher struct {
    Name string
    Salary int
}

func (p Student) Getname() string{   //實作Getname方法
    fmt.Println(p.Name )
    return p.Name
}

func (p Student) Running()  {   // 實作 Running方法
    fmt.Printf("%s running",p.Name)
}


func (p Teacher) Getname() string{   //實作Getname方法
    fmt.Println(p.Name )
    return p.Name
}

func (p Teacher) Running()  {   // 實作 Running方法
    fmt.Printf("\n%s running",p.Name)
}
func main()  {
    var skill Skills
    var stu1 Student
    var t1 Teacher
    t1.Name = "wang"
    stu1.Name = "wd"
    stu1.Age = 22
    skill = stu1
    skill.Running()
    skill = t1
    t1.Running()
}
//wd running
//wang running      
【go語言】

接口嵌套

go語言中的接口可以嵌套,可以了解我繼承,子接口擁有父接口的所有方法,并且想要使用該子接口的話,必須将父接口和子接口的所有方法都實作。

【go語言】
type Skills interface {
    Running()
    Getname() string

}

type Test interface {
    sleeping()
    Skills   //繼承Skills
}      
【go語言】

類型轉換

由于接口是一般類型,當我們使用接口時候可能不知道它是那個類型實作的,基本資料類型我們有對應的方法進行類型轉換,當然接口類型也有類型轉換。

當然我們也可以用這個方式來進行類型的判斷。

轉換方式:

var s int
var x interface

x = s
y , ok := x.(int)  //将interface 轉為int,ok可省略 但是省略以後轉換失敗會報錯,true轉換成功,false轉換失敗, 并采用預設值      

示例:

【go語言】
package main

import "fmt"

func main()  {
    var x interface{}

    s := "WD"
    x = s
    y,ok := x.(int)
    z,ok1 := x.(string)
    fmt.Println(y,ok)
    fmt.Println(z,ok1)
}
//0 false
//WD true      
【go語言】

判斷類型示例:

【go語言】
package main

import "fmt"

type Student struct {
    Name string
}

func TestType(items ...interface{}) {
    for k, v := range items {
        switch v.(type) {
        case string:
        fmt.Printf("type is string, %d[%v]\n", k, v)
        case bool:
        fmt.Printf("type is bool, %d[%v]\n", k, v)
        case int:
        fmt.Printf("type is int, %d[%v]\n", k, v)
        case float32, float64:
        fmt.Printf("type is float, %d[%v]\n", k, v)
        case Student:
        fmt.Printf("type is Student, %d[%v]\n", k, v)
        case *Student:
        fmt.Printf("type is Student, %d[%p]\n", k, v)
        }
}
}

func main() {
 var stu Student
TestType("WD", 100, stu,3.3)
}
//type is string, 0[WD]
//type is int, 1[100]
//type is Student, 2[{}]
//type is float, 3[3.3]      
【go語言】

二、反射reflect

反射是程式執行時檢查其所擁有的結構。尤其是類型的一種能力。這是元程式設計的一種形式。它同一時候也是造成混淆的重要來源。

每一個語言的反射模型都不同(同一時候很多語言根本不支援反射,python通過hasattr方法實作)

go語言中的反射通過refect包實作,reflect包實作了運作時反射,允許程式操作任意類型的對象。

在介紹反射之前先說明下reflect包中的兩個資料類Type和Value。

Type

Type:Type類型用來表示一個go類型。

不是所有go類型的Type值都能使用所有方法。請參見每個方法的文檔擷取使用限制。在調用有分類限定的方法時,應先使用Kind方法獲知類型的分類。調用該分類不支援的方法會導緻運作時的panic。

擷取Type對象的方法:

func TypeOf(i interface{}) Type      
【go語言】
package main

import (
    "reflect"
    "fmt"
)

func main() {
    str := "wd"
    res_type := reflect.TypeOf(str)
    fmt.Println(res_type) //string
}      
【go語言】

reflect.Type中方法

通用方法:

【go語言】
// 通用方法

func (t *rtype) String() string // 擷取 t 類型的字元串描述,不要通過 String 來判斷兩種類型是否一緻。

func (t *rtype) Name() string // 擷取 t 類型在其包中定義的名稱,未命名類型則傳回空字元串。

func (t *rtype) PkgPath() string // 擷取 t 類型所在包的名稱,未命名類型則傳回空字元串。

func (t *rtype) Kind() reflect.Kind // 擷取 t 類型的類别。

func (t *rtype) Size() uintptr // 擷取 t 類型的值在配置設定記憶體時的大小,功能和 unsafe.SizeOf 一樣。

func (t *rtype) Align() int  // 擷取 t 類型的值在配置設定記憶體時的位元組對齊值。

func (t *rtype) FieldAlign() int  // 擷取 t 類型的值作為結構體字段時的位元組對齊值。

func (t *rtype) NumMethod() int  // 擷取 t 類型的方法數量。

func (t *rtype) Method() reflect.Method  // 根據索引擷取 t 類型的方法,如果方法不存在,則 panic。
// 如果 t 是一個實際的類型,則傳回值的 Type 和 Func 字段會列出接收者。
// 如果 t 隻是一個接口,則傳回值的 Type 不列出接收者,Func 為空值。

func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根據名稱擷取 t 類型的方法。

func (t *rtype) Implements(u reflect.Type) bool // 判斷 t 類型是否實作了 u 接口。

func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判斷 t 類型的值可否轉換為 u 類型。

func (t *rtype) AssignableTo(u reflect.Type) bool // 判斷 t 類型的值可否指派給 u 類型。

func (t *rtype) Comparable() bool // 判斷 t 類型的值可否進行比較操作      
####注意對于:數組、切片、映射、通道、指針、接口 
func (t *rtype) Elem() reflect.Type // 擷取元素類型、擷取指針所指對象類型,擷取接口的動态類型      
【go語言】
【go語言】
package main

import (
    "fmt"
    "reflect"
)

type Skills interface {
    reading()
    running()
}

type Student struct {
    Name string
    Age   int

}

func (self Student) runing(){
    fmt.Printf("%s is running\n",self.Name)
}
func (self Student) reading(){
    fmt.Printf("%s is reading\n" ,self.Name)
}
func main() {
    stu1 := Student{Name:"wd",Age:22}
    inf := new(Skills)
    stu_type := reflect.TypeOf(stu1)
    inf_type := reflect.TypeOf(inf).Elem()   // 特别說明,引用類型需要用Elem()擷取指針所指的對象類型
    fmt.Println(stu_type.String())  //main.Student
    fmt.Println(stu_type.Name()) //Student
    fmt.Println(stu_type.PkgPath()) //main
    fmt.Println(stu_type.Kind()) //struct
    fmt.Println(stu_type.Size())  //24
    fmt.Println(inf_type.NumMethod())  //2
    fmt.Println(inf_type.Method(0),inf_type.Method(0).Name)  // {reading main func() <invalid Value> 0} reading
    fmt.Println(inf_type.MethodByName("reading")) //{reading main func() <invalid Value> 0} true

}      
【go語言】

其他方法:

【go語言】
// 數值

func (t *rtype) Bits() int  // 擷取數值類型的位寬,t 必須是整型、浮點型、複數型

------------------------------

// 數組

func (t *rtype) Len() int  // 擷取數組的元素個數

------------------------------

// 映射

func (t *rtype) Key() reflect.Type // 擷取映射的鍵類型

------------------------------

// 通道


func (t *rtype) ChanDir() reflect.ChanDir // 擷取通道的方向

------------------------------

// 結構體


func (t *rtype) NumField() int  // 擷取字段數量

func (t *rtype) Field(int) reflect.StructField  // 根據索引擷取字段

func (t *rtype) FieldByName(string) (reflect.StructField, bool)  // 根據名稱擷取字段

func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool)  // 根據指定的比對函數 math 擷取字段

func (t *rtype) FieldByIndex(index []int) reflect.StructField  // 根據索引鍊擷取嵌套字段

------------------------------

// 函數


func (t *rtype) NumIn() int // 擷取函數的參數數量

func (t *rtype) In(int) reflect.Type // 根據索引擷取函數的參數資訊

func (t *rtype) NumOut() int // 擷取函數的傳回值數量

func (t *rtype) Out(int) reflect.Type // 根據索引擷取函數的傳回值資訊

func (t *rtype) IsVariadic() bool  // 判斷函數是否具有可變參數。
// 如果有可變參數,則 t.In(t.NumIn()-1) 将傳回一個切片。      
【go語言】
【go語言】
package main

import (
    "fmt"
    "reflect"
)

type Skills interface {
    reading()
    running()
}

type Student struct {
    Name string
    Age   int

}

func (self Student) runing(){
    fmt.Printf("%s is running\n",self.Name)
}
func (self Student) reading(){
    fmt.Printf("%s is reading\n" ,self.Name)
}
func main() {
    stu1 := Student{Name:"wd",Age:22}
    stu_type := reflect.TypeOf(stu1)
    fmt.Println(stu_type.NumField())  //2
    fmt.Println(stu_type.Field(0))  //{Name  string  0 [0] false}
    fmt.Println(stu_type.FieldByName("Age"))  //{{Age  int  16 [1] false} true
}      
【go語言】

Value

不是所有go類型值的Value表示都能使用所有方法。請參見每個方法的文檔擷取使用限制。在調用有分類限定的方法時,應先使用Kind方法獲知該值的分類。調用該分類不支援的方法會導緻運作時的panic。

Value為go值提供了反射接口,擷取Value對象方法:

func ValueOf(i interface{}) Value      
str := "wd"
val := reflect.ValueOf(str)
//wd      

reflect.Value方法

注意:以下所有方法中的v是reflect.Value傳回的值。

reflect.Value.Kind():擷取變量類别,傳回常量

【go語言】

 常量類型

【go語言】
package main

import (
"reflect"
    "fmt"
)

func main() {
    str := "wd"
    val := reflect.ValueOf(str).Kind()
    fmt.Println(val)//string
}      
【go語言】

用于擷取值方法:

【go語言】
func (v Value) Int() int64 // 擷取int類型值,如果 v 值不是有符号整型,則 panic。

func (v Value) Uint() uint64 // 擷取unit類型的值,如果 v 值不是無符号整型(包括 uintptr),則 panic。

func (v Value) Float() float64 // 擷取float類型的值,如果 v 值不是浮點型,則 panic。

func (v Value) Complex() complex128 // 擷取複數類型的值,如果 v 值不是複數型,則 panic。

func (v Value) Bool() bool // 擷取布爾類型的值,如果 v 值不是布爾型,則 panic。

func (v Value) Len() int // 擷取 v 值的長度,v 值必須是字元串、數組、切片、映射、通道。

func (v Value) Cap() int  // 擷取 v 值的容量,v 值必須是數值、切片、通道。

func (v Value) Index(i int) reflect.Value // 擷取 v 值的第 i 個元素,v 值必須是字元串、數組、切片,i 不能超出範圍。

func (v Value) Bytes() []byte // 擷取位元組類型的值,如果 v 值不是位元組切片,則 panic。

func (v Value) Slice(i, j int) reflect.Value // 擷取 v 值的切片,切片長度 = j - i,切片容量 = v.Cap() - i。
// v 必須是字元串、數值、切片,如果是數組則必須可尋址。i 不能超出範圍。

func (v Value) Slice3(i, j, k int) reflect.Value  // 擷取 v 值的切片,切片長度 = j - i,切片容量 = k - i。
// i、j、k 不能超出 v 的容量。i <= j <= k。
// v 必須是字元串、數值、切片,如果是數組則必須可尋址。i 不能超出範圍。

func (v Value) MapIndex(key Value) reflect.Value // 根據 key 鍵擷取 v 值的内容,v 值必須是映射。
// 如果指定的元素不存在,或 v 值是未初始化的映射,則傳回零值(reflect.ValueOf(nil))

func (v Value) MapKeys() []reflect.Value // 擷取 v 值的所有鍵的無序清單,v 值必須是映射。
// 如果 v 值是未初始化的映射,則傳回空清單。

func (v Value) OverflowInt(x int64) bool // 判斷 x 是否超出 v 值的取值範圍,v 值必須是有符号整型。

func (v Value) OverflowUint(x uint64) bool  // 判斷 x 是否超出 v 值的取值範圍,v 值必須是無符号整型。

func (v Value) OverflowFloat(x float64) bool  // 判斷 x 是否超出 v 值的取值範圍,v 值必須是浮點型。

func (v Value) OverflowComplex(x complex128) bool // 判斷 x 是否超出 v 值的取值範圍,v 值必須是複數型。      
【go語言】

設定值方法:

【go語言】
func (v Value) SetInt(x int64)  //設定int類型的值

func (v Value) SetUint(x uint64)  // 設定無符号整型的值

func (v Value) SetFloat(x float64) // 設定浮點類型的值

func (v Value) SetComplex(x complex128) //設定複數類型的值

func (v Value) SetBool(x bool) //設定布爾類型的值

func (v Value) SetString(x string) //設定字元串類型的值

func (v Value) SetLen(n int)  // 設定切片的長度,n 不能超出範圍,不能為負數。

func (v Value) SetCap(n int) //設定切片的容量

func (v Value) SetBytes(x []byte) //設定位元組類型的值

func (v Value) SetMapIndex(key, val reflect.Value) //設定map的key和value,前提必須是初始化以後,存在覆寫、不存在添加      
【go語言】
【go語言】
##########結構體相關:
func (v Value) NumField() int // 擷取結構體字段(成員)數量

func (v Value) Field(i int) reflect.Value  //根據索引擷取結構體字段

func (v Value) FieldByIndex(index []int) reflect.Value // 根據索引鍊擷取結構體嵌套字段

func (v Value) FieldByName(string) reflect.Value // 根據名稱擷取結構體的字段,不存在傳回reflect.ValueOf(nil)

func (v Value) FieldByNameFunc(match func(string) bool) Value // 根據比對函數 match 擷取字段,如果沒有比對的字段,則傳回零值(reflect.ValueOf(nil))


########通道相關:
func (v Value) Send(x reflect.Value)// 發送資料(會阻塞),v 值必須是可寫通道。

func (v Value) Recv() (x reflect.Value, ok bool) // 接收資料(會阻塞),v 值必須是可讀通道。

func (v Value) TrySend(x reflect.Value) bool // 嘗試發送資料(不會阻塞),v 值必須是可寫通道。

func (v Value) TryRecv() (x reflect.Value, ok bool) // 嘗試接收資料(不會阻塞),v 值必須是可讀通道。

func (v Value) Close() // 關閉通道


########函數相關
func (v Value) Call(in []Value) (r []Value) // 通過參數清單 in 調用 v 值所代表的函數(或方法)。函數的傳回值存入 r 中傳回。
// 要傳入多少參數就在 in 中存入多少元素。
// Call 即可以調用定參函數(參數數量固定),也可以調用變參函數(參數數量可變)。

func (v Value) CallSlice(in []Value) []Value // 調用變參函數      
【go語言】

示例一:擷取和設定普通類型的值

【go語言】
package main

import (
    "reflect"
    "fmt"
)

func main() {
    str := "wd"
    age := 11
    fmt.Println(reflect.ValueOf(str).String()) //擷取str的值,結果wd
    fmt.Println(reflect.ValueOf(age).Int())   //擷取age的值,結果age
    str2 := reflect.ValueOf(&str)        //擷取Value類型
    str2.Elem().SetString("jack")     //設定值
    fmt.Println(str2.Elem(),age) //jack 11
}      
【go語言】

示例二:簡單結構體操作

【go語言】
package main

import (
    "fmt"
    "reflect"
)

type Skills interface {
    reading()
    running()
}

type Student struct {
    Name string
    Age   int

}

func (self Student) runing(){
    fmt.Printf("%s is running\n",self.Name)
}
func (self Student) reading(){
    fmt.Printf("%s is reading\n" ,self.Name)
}
func main() {
    stu1 := Student{Name:"wd",Age:22}
    stu_val := reflect.ValueOf(stu1) //擷取Value類型
    fmt.Println(stu_val.NumField()) //2
    fmt.Println(stu_val.Field(0),stu_val.Field(1)) //wd 22
    fmt.Println(stu_val.FieldByName("Age")) //22
    stu_val2 := reflect.ValueOf(&stu1).Elem()   
    stu_val2.FieldByName("Age").SetInt(33)  //設定字段值 ,結果33
    fmt.Println(stu1.Age)
    
}      
【go語言】

示例三:通過反射調用結構體中的方法,通過reflect.Value.Method(i int).Call()或者reflect.Value.MethodByName(name string).Call()實作

【go語言】
package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Name string
    Age int
}

func (this *Student) SetName(name string) {
    this.Name = name
    fmt.Printf("set name %s\n",this.Name )
}

func (this *Student) SetAge(age int) {
    this.Age = age
    fmt.Printf("set age %d\n",age )
}

func (this *Student) String() string {
    fmt.Printf("this is %s\n",this.Name)
    return this.Name
}

func main() {
    stu1 := &Student{Name:"wd",Age:22}
    val := reflect.ValueOf(stu1)       //擷取Value類型,也可以使用reflect.ValueOf(&stu1).Elem() 
    val.MethodByName("String").Call(nil)  //調用String方法

    params := make([]reflect.Value, 1)
    params[0] = reflect.ValueOf(18)
    val.MethodByName("SetAge").Call(params)  //通過名稱調用方法

    params[0] = reflect.ValueOf("jack")   
    val.Method(1).Call(params)    //通過方法索引調用

    fmt.Println(stu1.Name,stu1.Age)
}
//this is wd
//set age 18
//set name jack
//jack 18      
【go語言】