go 的方法集和接口斷言


Go 的方法集與接口斷言

方法集

引子

首先來看一段代碼:

package main

import "fmt"

func main() {
	var v IpmHelloByValue
	CallSayHello(v)  // Ok,Output: Hello,I'm value
	CallSayHello(&v) // Ok,Output: Hello,I'm value
	var p IpmHelloByPointer
	CallSayHello(p)  // Not Ok,compile failed: IpmHelloByPointer does not implement IHello (SayHello method has pointer receiver)
	CallSayHello(&p) // OK, Output: Hello,I'm pointer
}

type IHello interface {
	SayHello()
}

type IpmHelloByPointer struct {
}

func (p *IpmHelloByPointer) SayHello() {
	fmt.Println("Hello,I'm pointer")
}

type IpmHelloByValue struct {
}

func (v IpmHelloByValue) SayHello() {
	fmt.Println("Hello,I'm value")
}

func CallSayHello(h IHello) {
	h.SayHello()
}

為何 CallSayHello(p)會編譯失敗,這就涉及到方法集了。

介紹

[方法集(method set)][https://golang.org/ref/spec#Method_sets]:定義了一組關聯到給定類型的值或者指針的方法。在定義方法時所使用的接收者(receiver)的類型(值/指針),決定了該方法是關聯到值還是關聯到指針。

Method sets

A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.

The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.

類型的方法集決定了該類型所實現的接口,以及當使用該類型作為 receiver 時,所能調用的

Values Methods Receivers
T (t T)
*T (t T) and (t *T)

即:

  • T類型的,只能調用接收者類型的方法。
  • 指向T類型的指針,既能調用接收者類型指針的方法,也能調用接收者類型的方法。

例子

舉個例子,為一個結構體聲明兩個方法,其中一個方法的 receiver 是 value,另一個方法的 receiver 是 pointer。

type MyStruct struct {
}

// receiver 是一個 value
func (m MyStruct) ValueReceiver() {
	fmt.Println("ValueReceiver")
}

// receiver 是一個 pointer
func (m *MyStruct) PointerReceiver() {
	fmt.Println("PointerReceiver")
}

為這個 struct 創建兩個示例,一個的類型是 value,另一個的類型是 pointer。

func main() {
	var m MyStruct   // 方法集中只有 ValueReceiver()
	var pm *MyStruct // 方法集中既有 PointerReceiver(), 也有 ValueReceiver()
}

接下來創建兩個 interface 以及使用這兩個 interface 的函數

type IValue interface {
	ValueReceiver()
}

type IPointer interface {
	PointerReceiver()
}

func CallValue(v IValue) {
	v.ValueReceiver()
}

func CallPointer(p IPointer) {
	p.PointerReceiver()
}

分別將 m 和 pm 傳入這兩個函數會發生什么?

func main() {
	var m MyStruct   // 方法集中只有 ValueReceiver()
	var pm *MyStruct // 方法集中既有 PointerReceiver(), 也有 ValueReceiver()
	CallValue(m)	// OK
	// 因為 m 的方法集中並沒有 PointerReceiver(),所以編譯器說它沒有實現 IPointer 接口
	CallPointer(m)	// Compile failed:Type does not implement 'IPointer' as 'PointerReceiver' method has a pointer receiver
	CallValue(pm)	// OK
	CallPointer(pm)	// OK
}

一個例外?

package main

import "fmt"

func main() {
	var m MyStruct
	m.ValueReceiver()   // OK,Output: ValueReceiver
    m.PointerReceiver() // OK,Output: PointerReceiver 這里為什么可以調用 PointerReceiver()?
	pm := &m
	pm.ValueReceiver()   // OK,Output: ValueReceiver
	pm.PointerReceiver() // OK,Output: PointerReceiver
}

type MyStruct struct {
}

// receiver 是一個 value
func (m MyStruct) ValueReceiver() {
	fmt.Println("ValueReceiver")
}

// receiver 是一個 pointer
func (m *MyStruct) PointerReceiver() {
	fmt.Println("PointerReceiver")
}

重點在第8行,按照之前所說的,m的方法集中並沒有PointerReceiver()這個方法,為何這段代碼可以編譯成功?

這是因為編譯器在后面做了工作。

m.PointerReceiver()

這句代碼中,編譯器對它做了一個隱式的 dereference 操作,偷偷的將它變成了

(&m).PointerReceiver()

所以最終還是通過一個 pointer 作為 receiver 去調用的 PointerReceiver

但是當變量無法取得地址時,編譯器就無能為力了,比如這種:

MyStruct{}.ValueReceiver()      // OK
MyStruct{}.PointerReceiver()    // Not OK
(&MyStruct{}).PointerReceiver() // OK

因為編譯器無法取得一個臨時變量的地址。

接口斷言

簡介

接口斷言可以判斷一個 struct 是否實現了某個接口

通過

// 注意 _ 和 interfaceName 之間不要有 ','
var _ interfaceName = ImplementType

可以實現編譯期的接口斷言。

其中ImplementType既可以是一個 value,也可以是一個 pointer,如果是 value 類型,需要用 nil 來初始化。

例子

還是之前的例子:

type MyStruct struct {
}

// receiver 是一個 value
func (m MyStruct) ValueReceiver() {
	fmt.Println("ValueReceiver")
}

// receiver 是一個 pointer
func (m *MyStruct) PointerReceiver() {
	fmt.Println("PointerReceiver")
}

type IValue interface {
	ValueReceiver()
}

type IPointer interface {
	PointerReceiver()
}

加上接口斷言:

var _ IValue = (*MyStruct)(nil)   // OK
var _ IPointer = (*MyStruct)(nil) // OK
var _ IValue = MyStruct{}         // OK
var _ IPointer = MyStruct{}       // Not OK: Type does not implement 'IPointer'

我是笨比

看起來這個接口斷言好高大上呀,仔細一琢磨,它的形式不就是 go 中聲明變量的方式么?

var 變量名字 類型 = 表達式

這兒只不過是把變量名字用 _ 代替了而已,意思是告訴編譯器我不在乎這個變量的值。

總結:我是笨比(是什么迷惑住了我的雙眼?)

參考

《Go語言實戰》

《The go programming language》

https://blog.csdn.net/random_w/article/details/106279550


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM