亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Golang中interface接口的原理和使用技巧

 更新時(shí)間:2022年11月29日 16:04:56   作者:AllenWu  
interface?接口在?Go?語(yǔ)言里面的地位非常重要,是一個(gè)非常重要的數(shù)據(jù)結(jié)構(gòu)。本文主要介紹了Golang中interface接口的原理和使用技巧,希望對(duì)大家有所幫助

一、Go interface 介紹

interface 在 Go 中的重要性說(shuō)明

interface 接口在 Go 語(yǔ)言里面的地位非常重要,是一個(gè)非常重要的數(shù)據(jù)結(jié)構(gòu),只要是實(shí)際業(yè)務(wù)編程,并且想要寫(xiě)出優(yōu)雅的代碼,那么必然要用上 interface,因此 interface 在 Go 語(yǔ)言里面處于非常核心的地位。

我們都知道,Go 語(yǔ)言和典型的面向?qū)ο蟮恼Z(yǔ)言不太一樣,Go 在語(yǔ)法上是不支持面向?qū)ο蟮念?、繼承等相關(guān)概念的。但是,并不代表 Go 里面不能實(shí)現(xiàn)面向?qū)ο蟮囊恍┬袨楸热缋^承、多態(tài),在 Go 里面,通過(guò) interface 完全可以實(shí)現(xiàn)諸如 C++ 里面的繼承 和 多態(tài)的語(yǔ)法效果。

interface 的特性

Go 中的 interface 接口有如下特性:

關(guān)于接口的定義和簽名

  • 接口是一個(gè)或多個(gè)方法簽名的集合,接口只有方法聲明,沒(méi)有實(shí)現(xiàn),沒(méi)有數(shù)據(jù)字段,只要某個(gè)類型擁有該接口的所有方法簽名,那么就相當(dāng)于實(shí)現(xiàn)了該接口,無(wú)需顯示聲明了哪個(gè)接口,這稱為 Structural Typing。
  • interface 接口可以匿名嵌入其他接口中,或嵌入到 struct 結(jié)構(gòu)中
  • 接口可以支持匿名字段方法

關(guān)于接口賦值

  • 只有當(dāng)接口存儲(chǔ)的類型和對(duì)象都為 nil 時(shí),接口才等于 nil
  • 一個(gè)空的接口可以作為任何類型數(shù)據(jù)的容器
  • 如果兩個(gè)接口都擁有相同的方法,那么它們就是等同的,任何實(shí)現(xiàn)了他們這個(gè)接口的對(duì)象之間,都可以相互賦值
  • 如果某個(gè) struct 對(duì)象實(shí)現(xiàn)了某個(gè)接口的所有方法,那么可以直接將這個(gè) struct 的實(shí)例對(duì)象直接賦值給這個(gè)接口類型的變量。

關(guān)于接口嵌套,Go 里面支持接口嵌套,但是不支持遞歸嵌套

通過(guò)接口可以實(shí)現(xiàn)面向?qū)ο缶幊讨械亩鄳B(tài)的效果

interface 接口和 reflect 反射

在 Go 的實(shí)現(xiàn)里面,每個(gè) interface 接口變量都有一個(gè)對(duì)應(yīng) pair,這個(gè) pair 中記錄了接口的實(shí)際變量的類型和值(value, type),其中,value 是實(shí)際變量值,type 是實(shí)際變量的類型。任何一個(gè) interface{} 類型的變量都包含了2個(gè)指針,一個(gè)指針指向值的類型,對(duì)應(yīng) pair 中的 type,這個(gè) type 類型包括靜態(tài)的類型 (static type,比如 int、string...)和具體的類型(concrete type,interface 所指向的具體類型),另外一個(gè)指針指向?qū)嶋H的值,對(duì)應(yīng) pair 中的 value。

interface 及其 pair 的存在,是 Go 語(yǔ)言中實(shí)現(xiàn) reflect 反射的前提,理解了 pair,就更容易理解反射。反射就是用來(lái)檢測(cè)存儲(chǔ)在接口變量?jī)?nèi)部(值value;類型concrete type) pair 對(duì)的一種機(jī)制。

二、Go 里面為啥偏向使用 Interface

Go 里面為啥偏向使用 Interface 呢? 主要原因有如下幾點(diǎn):

可以實(shí)現(xiàn)泛型編程(雖然 Go 在 1.18 之后已經(jīng)支持泛型了)

在 C++ 等高級(jí)語(yǔ)言中使用泛型編程非常的簡(jiǎn)單,但是 Go 在 1.18 版本之前,是不支持泛型的,而通過(guò) Go 的接口,可以實(shí)現(xiàn)類似的泛型編程,如下是一個(gè)參考示例

    package sort

    // A type, typically a collection, that satisfies sort.Interface can be
    // sorted by the routines in this package.  The methods require that the
    // elements of the collection be enumerated by an integer index.
    type Interface interface {
        // Len is the number of elements in the collection.
        Len() int
        // Less reports whether the element with
        // index i should sort before the element with index j.
        Less(i, j int) bool
        // Swap swaps the elements with indexes i and j.
        Swap(i, j int)
    }
    
    ...
    
    // Sort sorts data.
    // It makes one call to data.Len to determine n, and O(n*log(n)) calls to
    // data.Less and data.Swap. The sort is not guaranteed to be stable.
    func Sort(data Interface) {
        // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
        n := data.Len()
        maxDepth := 0
        for i := n; i > 0; i >>= 1 {
            maxDepth++
        }
        maxDepth *= 2
        quickSort(data, 0, n, maxDepth)
    }

Sort 函數(shù)的形參是一個(gè) interface,包含了三個(gè)方法:Len(),Less(i,j int),Swap(i, j int)。使用的時(shí)候不管數(shù)組的元素類型是什么類型(int, float, string…),只要我們實(shí)現(xiàn)了這三個(gè)方法就可以使用 Sort 函數(shù),這樣就實(shí)現(xiàn)了“泛型編程”。

這種方式,我在閃聊項(xiàng)目里面也有實(shí)際應(yīng)用過(guò),具體案例就是對(duì)消息排序。 下面給一個(gè)具體示例,代碼能夠說(shuō)明一切,一看就懂:

    type Person struct {
    Name string
    Age  int
    }
    
    func (p Person) String() string {
        return fmt.Sprintf("%s: %d", p.Name, p.Age)
    }
    
    // ByAge implements sort.Interface for []Person based on
    // the Age field.
    type ByAge []Person //自定義
    
    func (a ByAge) Len() int           { return len(a) }
    func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
    func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
    
    func main() {
        people := []Person{
            {"Bob", 31},
            {"John", 42},
            {"Michael", 17},
            {"Jenny", 26},
        }
    
        fmt.Println(people)
        sort.Sort(ByAge(people))
        fmt.Println(people)
    }

可以隱藏具體的實(shí)現(xiàn)

隱藏具體的實(shí)現(xiàn),是說(shuō)我們提供給外部的一個(gè)方法(函數(shù)),但是我們是通過(guò) interface 接口的方式提供的,對(duì)調(diào)用方來(lái)說(shuō),只能通過(guò) interface 里面的方法來(lái)做一些操作,但是內(nèi)部的具體實(shí)現(xiàn)是完全不知道的。

例如我們常用的 context 包,就是這樣設(shè)計(jì)的,如果熟悉 Context 具體實(shí)現(xiàn)的就會(huì)很容易理解。詳細(xì)代碼如下:

    func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
        c := newCancelCtx(parent)
        propagateCancel(parent, &c)
        return &c, func() { c.cancel(true, Canceled) }
    }

可以看到 WithCancel 函數(shù)返回的還是一個(gè) Context interface,但是這個(gè) interface 的具體實(shí)現(xiàn)是 cancelCtx struct。

        // newCancelCtx returns an initialized cancelCtx.
        func newCancelCtx(parent Context) cancelCtx {
            return cancelCtx{
                Context: parent,
                done:    make(chan struct{}),
            }
        }
        
        // A cancelCtx can be canceled. When canceled, it also cancels any children
        // that implement canceler.
        type cancelCtx struct {
            Context     //注意一下這個(gè)地方
        
            done chan struct{} // closed by the first cancel call.
            mu       sync.Mutex
            children map[canceler]struct{} // set to nil by the first cancel call
            err      error                 // set to non-nil by the first cancel call
        }
        
        func (c *cancelCtx) Done() <-chan struct{} {
            return c.done
        }
        
        func (c *cancelCtx) Err() error {
            c.mu.Lock()
            defer c.mu.Unlock()
            return c.err
        }
        
        func (c *cancelCtx) String() string {
            return fmt.Sprintf("%v.WithCancel", c.Context)
        }

盡管內(nèi)部實(shí)現(xiàn)上下面三個(gè)函數(shù)返回的具體 struct (都實(shí)現(xiàn)了 Context interface)不同,但是對(duì)于使用者來(lái)說(shuō)是完全無(wú)感知的。

    func WithCancel(parent Context) (ctx Context, cancel CancelFunc)    //返回 cancelCtx
    func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) //返回 timerCtx
    func WithValue(parent Context, key, val interface{}) Context    //返回 valueCtx

可以實(shí)現(xiàn)面向?qū)ο缶幊讨械亩鄳B(tài)用法

interface 只是定義一個(gè)或一組方法函數(shù),但是這些方法只有函數(shù)簽名,沒(méi)有具體的實(shí)現(xiàn),這個(gè) C++ 中的虛函數(shù)非常類似。在 Go 里面,如果某個(gè)數(shù)據(jù)類型實(shí)現(xiàn) interface 中定義的那些函數(shù),則稱這些數(shù)據(jù)類型實(shí)現(xiàn)(implement)了這個(gè)接口 interface,這是我們常用的 OO 方式,如下是一個(gè)簡(jiǎn)單的示例

    // 定義一個(gè) SimpleLog 接口
    type SimpleLog interface {
        Print()
    }
    
    func TestFunc(x SimpleLog) {}
   
    // 定義一個(gè) PrintImpl 結(jié)構(gòu),用來(lái)實(shí)現(xiàn) SimpleLog 接口
    type PrintImpl struct {}
    // PrintImpl 對(duì)象實(shí)現(xiàn)了SimpleLog 接口的所有方法(本例中是 Print 方法),就說(shuō)明實(shí)現(xiàn)了  SimpleLog 接口
    func (p *PrintImpl) Print() {
    
    }
    
    func main() {
        var p PrintImpl
        TestFunc(p)
    }

空接口可以接受任何類型的參數(shù)

空接口比較特殊,它不包含任何方法:interface{} ,在 Go 語(yǔ)言中,所有其它數(shù)據(jù)類型都實(shí)現(xiàn)了空接口,如下:

var v1 interface{} = 1
var v2 interface{} = "abc"
var v3 interface{} = struct{ X int }{1}

因此,當(dāng)我們給 func 定義了一個(gè) interface{} 類型的參數(shù)(也就是一個(gè)空接口)之后,那么這個(gè)參數(shù)可以接受任何類型,官方包中最典型的例子就是標(biāo)準(zhǔn)庫(kù) fmt 包中的 Print 和 Fprint 系列的函數(shù)。

一個(gè)簡(jiǎn)單的定義示例方法如下:

	Persist(context context.Context, msg interface{}) bool

msg 可以為任何類型,如 pb.MsgInfo or pb.GroupMsgInfo,定義方法的時(shí)候可以統(tǒng)一命名模塊,實(shí)現(xiàn)的時(shí)候,根據(jù)不同場(chǎng)景實(shí)現(xiàn)不同方法。

三、Go interface 的常見(jiàn)應(yīng)用和實(shí)戰(zhàn)技巧

interface 接口賦值

可以將一個(gè)實(shí)現(xiàn)接口的對(duì)象實(shí)例賦值給接口,也可以將另外一個(gè)接口賦值給接口。

通過(guò)對(duì)象實(shí)例賦值

將一個(gè)對(duì)象實(shí)例賦值給一個(gè)接口之前,要保證該對(duì)象實(shí)現(xiàn)了接口的所有方法。在 Go 語(yǔ)言中,一個(gè)類只需要實(shí)現(xiàn)了接口要求的所有函數(shù),我們就說(shuō)這個(gè)類實(shí)現(xiàn)了該接口,這個(gè)是非侵入式接口的設(shè)計(jì)模式,非侵入式接口一個(gè)很重要的優(yōu)勢(shì)就是可以免去面向?qū)ο罄锩婺翘妆容^復(fù)雜的類的繼承體系。

在 Go 里面,面向?qū)ο蟮哪翘最惖睦^承體系就不需要關(guān)心了,定義接口的時(shí)候,我們只需關(guān)心這個(gè)接口應(yīng)該提供哪些方法,當(dāng)然,按照 Go 的原則,接口的功能要盡可能的保證職責(zé)單一。而對(duì)應(yīng)接口的實(shí)現(xiàn),也就是接口的調(diào)用方,我們只需要知道這個(gè)接口定義了哪些方法,然后我們實(shí)現(xiàn)這些方法就可以了,這個(gè)也無(wú)需提前規(guī)劃,調(diào)用方也無(wú)需關(guān)系是否有其他模塊定義過(guò)類似的接口或者實(shí)現(xiàn),只關(guān)注自身就行。

考慮如下示例:

type Integer int
func (a Integer) Less(b Integer) bool {
    return a < b
}
func (a *Integer) Add(b Integer) {
    *a += b
}
type LessAdder interface { 
    Less(b Integer) bool 
    Add(b Integer)
}
var a Integer = 1
var b1 LessAdder = &a  //OK
var b2 LessAdder = a   //not OK

b2 的賦值會(huì)報(bào)編譯錯(cuò)誤,為什么呢?因?yàn)檫@個(gè):The method set of any other named type T consists of all methods with receiver type T. The method set of the corresponding pointer type T is the set of all methods with receiver T or T (that is, it also contains the method set of T). 也就是說(shuō) *Integer 這個(gè)指針類型實(shí)現(xiàn)了接口 LessAdder 的所有方法,而 Integer 只實(shí)現(xiàn)了 Less 方法,所以不能賦值。

通過(guò)接口賦值

        var r io.Reader = new(os.File)
        var rw io.ReadWriter = r   //not ok
        var rw2 io.ReadWriter = new(os.File)
        var r2 io.Reader = rw2    //ok

因?yàn)?r 沒(méi)有 Write 方法,所以不能賦值給rw。

interface 接口嵌套

io package 中的一個(gè)接口:

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
    Reader
    Writer
}

ReadWriter 接口嵌套了 io.Reader 和 io.Writer 兩個(gè)接口,實(shí)際上,它等同于下面的寫(xiě)法:

type ReadWriter interface {
    Read(p []byte) (n int, err error) 
    Write(p []byte) (n int, err error)
}

注意,Go 語(yǔ)言中的接口不能遞歸嵌套,如下:

// illegal: Bad cannot embed itself
type Bad interface {
    Bad
}
// illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface {
    Bad2
}
type Bad2 interface {
    Bad1
}

interface 強(qiáng)制類型轉(zhuǎn)換

ret, ok := interface.(type) 斷言

在 Go 語(yǔ)言中,可以通過(guò) interface.(type) 的方式來(lái)對(duì)一個(gè) interface 進(jìn)行強(qiáng)制類型轉(zhuǎn)換,但是如果這個(gè) interface 被轉(zhuǎn)換為一個(gè)不包含指定類型的類型,那么就會(huì)出現(xiàn) panic 。因此,實(shí)戰(zhàn)應(yīng)用中,我們通常都是通過(guò) ret, ok := interface.(type) 這種斷言的方式來(lái)優(yōu)雅的進(jìn)行轉(zhuǎn)換,這個(gè)方法中第一個(gè)返回值是對(duì)應(yīng)類型的值,第二個(gè)返回值是類型是否正確,只有 ok = true 的情況下,才說(shuō)明轉(zhuǎn)換成功,最重要的是,通過(guò)這樣的轉(zhuǎn)換方式可以避免直接轉(zhuǎn)換如果類型不對(duì)的情況下產(chǎn)生 panic。

如下是一個(gè)以 string 為類型的示例:

str, ok := value.(string)
if ok {
    fmt.Printf("string value is: %q\n", str)
} else {
    fmt.Printf("value is not a string\n")
}

如果類型斷言失敗,則str將依然存在,并且類型為字符串,不過(guò)其為零值,即一個(gè)空字符串。

switch x.(type) 斷言

查詢接口類型的方式為:

switch x.(type) {
    // cases :
}

示例如下:

var value interface{} // Value provided by caller.
switch str := value.(type) {
case string:
    return str //type of str is string
case int: 
    return int //type of str is int
}

語(yǔ)句switch中的value必須是接口類型,變量str的類型為轉(zhuǎn)換后的類型。

interface 與 nil 的比較

interface 與 nil 的比較是挺有意思的,例子是最好的說(shuō)明,如下例子:

package main

import (
	"fmt"
	"reflect"
)

type State struct{}

func testnil1(a, b interface{}) bool {
	return a == b
}

func testnil2(a *State, b interface{}) bool {
	return a == b
}

func testnil3(a interface{}) bool {
	return a == nil
}

func testnil4(a *State) bool {
	return a == nil
}

func testnil5(a interface{}) bool {
	v := reflect.ValueOf(a)
	return !v.IsValid() || v.IsNil()
}

func main() {
	var a *State
	fmt.Println(testnil1(a, nil))
	fmt.Println(testnil2(a, nil))
	fmt.Println(testnil3(a))
	fmt.Println(testnil4(a))
	fmt.Println(testnil5(a))
}

運(yùn)行后返回的結(jié)果如下

false
false
false
true
true

為什么是這個(gè)結(jié)果?

*因?yàn)橐粋€(gè) interface{} 類型的變量包含了2個(gè)指針,一個(gè)指針指向值的類型,另外一個(gè)指針指向?qū)嶋H的值。對(duì)一個(gè) interface{} 類型的 nil 變量來(lái)說(shuō),它的兩個(gè)指針都是0;但是 var a State 傳進(jìn)去后,指向的類型的指針不為0了,因?yàn)橛蓄愋土耍?所以比較為 false。 interface 類型比較, 要是兩個(gè)指針都相等,才能相等。

到此這篇關(guān)于詳解Golang中interface接口的原理和使用技巧的文章就介紹到這了,更多相關(guān)Golang interface接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Go?中的時(shí)間處理

    詳解Go?中的時(shí)間處理

    這篇文章主要介紹了Go?中的時(shí)間處理,本文將介紹?time?庫(kù)中一些重要的函數(shù)和方法,希望能幫助到那些一遇到?Go?時(shí)間處理問(wèn)題就需要百度的童鞋,需要的朋友可以參考下
    2022-07-07
  • 協(xié)同開(kāi)發(fā)巧用gitignore中間件避免網(wǎng)絡(luò)請(qǐng)求攜帶登錄信息

    協(xié)同開(kāi)發(fā)巧用gitignore中間件避免網(wǎng)絡(luò)請(qǐng)求攜帶登錄信息

    這篇文章主要為大家介紹了協(xié)同開(kāi)發(fā)巧用gitignore中間件避免網(wǎng)絡(luò)請(qǐng)求攜帶登錄信息
    2022-06-06
  • 淺析golang如何在多線程中避免CPU指令重排

    淺析golang如何在多線程中避免CPU指令重排

    這篇文章主要為大家詳細(xì)介紹了golang在多線程中避免CPU指令重排的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-03-03
  • 詳解Golang中使用map時(shí)的注意問(wèn)題

    詳解Golang中使用map時(shí)的注意問(wèn)題

    Golang中的map是一種數(shù)據(jù)結(jié)構(gòu),它允許你使用鍵值對(duì)的形式存儲(chǔ)和訪問(wèn)數(shù)據(jù),map在Go中是非排序的,提供了高效查找、插入和刪除元素的能力,特別是當(dāng)鍵是不可變類型,本文給大家詳細(xì)介紹了Golang中使用map時(shí)的注意問(wèn)題,需要的朋友可以參考下
    2024-06-06
  • Go語(yǔ)言實(shí)現(xiàn)冒泡排序、選擇排序、快速排序及插入排序的方法

    Go語(yǔ)言實(shí)現(xiàn)冒泡排序、選擇排序、快速排序及插入排序的方法

    這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)冒泡排序、選擇排序、快速排序及插入排序的方法,以實(shí)例形式詳細(xì)分析了幾種常見(jiàn)的排序技巧與實(shí)現(xiàn)方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-02-02
  • Go語(yǔ)言操作數(shù)據(jù)庫(kù)及其常規(guī)操作的示例代碼

    Go語(yǔ)言操作數(shù)據(jù)庫(kù)及其常規(guī)操作的示例代碼

    這篇文章主要介紹了Go語(yǔ)言操作數(shù)據(jù)庫(kù)及其常規(guī)操作的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • go語(yǔ)言反射的基礎(chǔ)教程示例

    go語(yǔ)言反射的基礎(chǔ)教程示例

    這篇文章主要為大家介紹了go語(yǔ)言反射的基礎(chǔ)教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • Go語(yǔ)言實(shí)現(xiàn)Fibonacci數(shù)列的方法

    Go語(yǔ)言實(shí)現(xiàn)Fibonacci數(shù)列的方法

    這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)Fibonacci數(shù)列的方法,實(shí)例分析了使用遞歸和不使用遞歸兩種技巧,并對(duì)算法的效率進(jìn)行了對(duì)比,需要的朋友可以參考下
    2015-02-02
  • Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀

    Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀

    這篇文章主要為大家介紹了Go channel發(fā)送方和接收方如何相互阻塞等待源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • 一文徹底理解Golang閉包實(shí)現(xiàn)原理

    一文徹底理解Golang閉包實(shí)現(xiàn)原理

    閉包對(duì)于一個(gè)長(zhǎng)期寫(xiě)Java的開(kāi)發(fā)者來(lái)說(shuō)估計(jì)鮮有耳聞,光這名字感覺(jué)就有點(diǎn)"神秘莫測(cè)"。這篇文章的主要目的就是從編譯器的角度來(lái)分析閉包,徹底搞懂閉包的實(shí)現(xiàn)原理,需要的可以參考一下
    2022-10-10

最新評(píng)論