日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

golang 接口类型 interface 简介使用

發布時間:2025/3/15 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 golang 接口类型 interface 简介使用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. Go 語言與鴨子類型的關系

先直接來看維基百科里的定義:

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

翻譯過來就是:如果某個東西長得像鴨子,像鴨子一樣游泳,像鴨子一樣嘎嘎叫,那它就可以被看成是一只鴨子。

Duck Typing,鴨子類型,是動態編程語言的一種對象推斷策略,它更關注對象能如何被使用,而不是對象的類型本身。Go 語言作為一門靜態語言,它通過通過接口的方式完美支持鴨子類型。

例如,在動態語言 python 中,定義一個這樣的函數:

def hello_world(coder):coder.say_hello()

當調用此函數的時候,可以傳入任意類型,只要它實現了?say_hello()?函數就可以。如果沒有實現,運行過程中會出現錯誤。

而在靜態語言如 Java, C++ 中,必須要顯示地聲明實現了某個接口,之后,才能用在任何需要這個接口的地方。如果你在程序中調用?hello_world?函數,卻傳入了一個根本就沒有實現?say_hello()?的類型,那在編譯階段就不會通過。這也是靜態語言比動態語言更安全的原因。

動態語言和靜態語言的差別在此就有所體現。靜態語言在編譯期間就能發現類型不匹配的錯誤,不像動態語言,必須要運行到那一行代碼才會報錯。插一句,這也是我不喜歡用?python?的一個原因。當然,靜態語言要求程序員在編碼階段就要按照規定來編寫程序,為每個變量規定數據類型,這在某種程度上,加大了工作量,也加長了代碼量。動態語言則沒有這些要求,可以讓人更專注在業務上,代碼也更短,寫起來更快,這一點,寫 python 的同學比較清楚。

Go 語言作為一門現代靜態語言,是有后發優勢的。它引入了動態語言的便利,同時又會進行靜態語言的類型檢查,寫起來是非常 Happy 的。Go 采用了折中的做法:不要求類型顯示地聲明實現了某個接口,只要實現了相關的方法即可,編譯器就能檢測到。

來看個例子:

先定義一個接口,和使用此接口作為參數的函數:

type IGreeting interface {sayHello() }func sayHello(i IGreeting) {i.sayHello() }

再來定義兩個結構體:

type Go struct {} func (g Go) sayHello() {fmt.Println("Hi, I am GO!") }type PHP struct {} func (p PHP) sayHello() {fmt.Println("Hi, I am PHP!") }

最后,在 main 函數里調用 sayHello() 函數:

func main() {golang := Go{}php := PHP{}sayHello(golang)sayHello(php) }

程序輸出:

Hi, I am GO! Hi, I am PHP!

在 main 函數中,調用?sayHello() 函數時,傳入了?golang, php?對象,它們并沒有顯式地聲明實現了 IGreeting 類型,只是實現了接口所規定的 sayHello() 函數。實際上,編譯器在調用 sayHello() 函數時,會隱式地將?golang, php?對象轉換成 IGreeting 類型,這也是靜態語言的類型檢查功能。

順帶再提一下動態語言的特點:

變量綁定的類型是不確定的,在運行期間才能確定
函數和方法可以接收任何類型的參數,且調用時不檢查參數類型
不需要實現接口

總結一下,鴨子類型是一種動態語言的風格,在這種風格中,一個對象有效的語義,不是由繼承自特定的類或實現特定的接口,而是由它”當前方法和屬性的集合”決定。Go 作為一種靜態語言,通過接口實現了?鴨子類型,實際上是 Go 的編譯器在其中作了隱匿的轉換工作。

2. 值接收者和指針接收者的區別

方法

方法能給用戶自定義的類型添加新的行為。它和函數的區別在于方法有一個接收者,給一個函數添加一個接收者,那么它就變成了方法。接收者可以是值接收者,也可以是指針接收者。

在調用方法的時候,值類型既可以調用值接收者的方法,也可以調用指針接收者的方法;指針類型既可以調用指針接收者的方法,也可以調用值接收者的方法。

也就是說,不管方法的接收者是什么類型,該類型的值和指針都可以調用,不必嚴格符合接收者的類型。

來看個例子:

package mainimport "fmt"type Person struct {age int }func (p Person) howOld() int {return p.age }func (p *Person) growUp() {p.age += 1 }func main() {// qcrao 是值類型qcrao := Person{age: 18}// 值類型 調用接收者也是值類型的方法fmt.Println(qcrao.howOld())// 值類型 調用接收者是指針類型的方法qcrao.growUp()fmt.Println(qcrao.howOld())// ----------------------// stefno 是指針類型stefno := &Person{age: 100}// 指針類型 調用接收者是值類型的方法fmt.Println(stefno.howOld())// 指針類型 調用接收者也是指針類型的方法stefno.growUp()fmt.Println(stefno.howOld()) }

上例子的輸出結果是:

18 19 100 101

調用了?growUp?函數后,不管調用者是值類型還是指針類型,它的?Age?值都改變了。

實際上,當類型和方法的接收者類型不同時,其實是編譯器在背后做了一些工作,用一個表格來呈現:

-值接收者指針接收者
值類型調用者方法會使用調用者的一個副本,類似于“傳值”使用值的引用來調用方法,上例中,qcrao.growUp()?實際上是?(&qcrao).growUp()
指針類型調用者指針被解引用為值,上例中,stefno.howOld()?實際上是?(*stefno).howOld()實際上也是“傳值”,方法里的操作會影響到調用者,類似于指針傳參,拷貝了一份指針

值接收者和指針接收者

前面說過,不管接收者類型是值類型還是指針類型,都可以通過值類型或指針類型調用,這里面實際上通過語法糖起作用的。

先說結論:實現了接收者是值類型的方法,相當于自動實現了接收者是指針類型的方法;而實現了接收者是指針類型的方法,不會自動生成對應接收者是值類型的方法。

來看一個例子,就會完全明白:

package mainimport "fmt"type coder interface {code()debug() }type Gopher struct {language string }func (p Gopher) code() {fmt.Printf("I am coding %s language\n", p.language) }func (p *Gopher) debug() {fmt.Printf("I am debuging %s language\n", p.language) }func main() {var c coder = &Gopher{"Go"}c.code()c.debug() }

上述代碼里定義了一個接口?coder,接口定義了兩個函數:

code() debug()

接著定義了一個結構體?Gopher,它實現了兩個方法,一個值接收者,一個指針接收者。

最后,我們在?main?函數里通過接口類型的變量調用了定義的兩個函數。

運行一下,結果:

I am coding Go language I am debuging Go language

但是如果我們把?main?函數的第一條語句換一下:

func main() {var c coder = Gopher{"Go"}c.code()c.debug() }

運行一下,報錯:

./main.go:23:6: cannot use Gopher literal (type Gopher) as type coder in assignment:Gopher does not implement coder (debug method has pointer receiver)

看出這兩處代碼的差別了嗎?第一次是將?&Gopher?賦給了?coder;第二次則是將?Gopher?賦給了?coder。

第二次報錯是說,Gopher?沒有實現?coder,很明顯了吧?因為?Gopher?類型并沒有實現?debug?方法。表面上看,?*Gopher?類型也沒有實現?code?方法,但是因為?Gopher?類型實現了?code?方法,所以讓?*Gopher?類型自動擁有了?code?方法。

當然,上面的說法有一個簡單的解釋:接收者是指針類型的方法,很可能在方法中會對接收者的屬性進行更改操作,從而影響接收者;而對于接收者是值類型的方法,在方法中不會對接收者本身產生影響。

所以,當實現了一個接收者是值類型的方法,就可以自動生成一個接收者是對應指針類型的方法,因為兩者都不會影響接收者。但是,當實現了一個接收者是指針類型的方法,如果此時自動生成一個接收者是值類型的方法,原本期望對接收者的改變(通過指針實現),現在無法實現,因為值類型會產生一個拷貝,不會真正影響調用者。

最后,只要記住下面這點就可以了:

如果實現了接收者是值類型的方法,會隱含地也實現了接收者是指針類型的方法。

兩者分別在何時使用

如果方法的接收者是值類型,無論調用者是對象還是對象指針,修改的都是對象的副本,不影響調用者;如果方法的接收者是指針類型,則調用者修改的是指針指向的對象本身。

使用指針作為方法的接收者的理由:

  • 方法能夠修改接收者指向的值。
  • 避免在每次調用方法時復制該值,在值的類型為大型結構體時,這樣做會更加高效。

是使用值接收者還是指針接收者,不是由該方法是否修改了調用者(也就是接收者)來決定,而是應該基于該類型的本質。

如果類型具備“原始的本質”,也就是說它的成員都是由 Go 語言里內置的原始類型,如字符串,整型值等,那就定義值接收者類型的方法。像內置的引用類型,如 slice,map,interface,channel,這些類型比較特殊,聲明他們的時候,實際上是創建了一個?header, 對于他們也是直接定義值接收者類型的方法。這樣,調用函數時,是直接 copy 了這些類型的?header,而?header?本身就是為復制設計的。

如果類型具備非原始的本質,不能被安全地復制,這種類型總是應該被共享,那就定義指針接收者的方法。比如 go 源碼里的文件結構體(struct File)就不應該被復制,應該只有一份實體。

這一段說的比較繞,大家可以去看《Go 語言實戰》5.3 那一節。

3. iface 和 eface 的區別是什么

iface?和?eface?都是 Go 中描述接口的底層結構體,區別在于?iface?描述的接口包含方法,而?eface?則是不包含任何方法的空接口:interface{}。

從源碼層面看一下:

type iface struct {tab *itabdata unsafe.Pointer }type itab struct {inter *interfacetype_type *_typelink *itabhash uint32 // copy of _type.hash. Used for type switches.bad bool // type does not implement interfaceinhash bool // has this itab been added to hash?unused [2]bytefun [1]uintptr // variable sized }

iface?內部維護兩個指針,tab?指向一個?itab?實體, 它表示接口的類型以及賦給這個接口的實體類型。data?則指向接口具體的值,一般而言是一個指向堆內存的指針。

再來仔細看一下?itab?結構體:_type?字段描述了實體的類型,包括內存對齊方式,大小等;inter?字段則描述了接口的類型。fun?字段放置和接口方法對應的具體數據類型的方法地址,實現接口調用方法的動態分派,一般在每次給接口賦值發生轉換時會更新此表,或者直接拿緩存的 itab。

這里只會列出實體類型和接口相關的方法,實體類型的其他方法并不會出現在這里。如果你學過 C++ 的話,這里可以類比虛函數的概念。

另外,你可能會覺得奇怪,為什么?fun?數組的大小為 1,要是接口定義了多個方法可怎么辦?實際上,這里存儲的是第一個方法的函數指針,如果有更多的方法,在它之后的內存空間里繼續存儲。從匯編角度來看,通過增加地址就能獲取到這些函數指針,沒什么影響。順便提一句,這些方法是按照函數名稱的字典序進行排列的。

再看一下?interfacetype?類型,它描述的是接口的類型:

type interfacetype struct {typ _typepkgpath namemhdr []imethod }

可以看到,它包裝了?_type?類型,_type?實際上是描述 Go 語言中各種數據類型的結構體。我們注意到,這里還包含一個?mhdr?字段,表示接口所定義的函數列表,?pkgpath?記錄定義了接口的包名。

這里通過一張圖來看下?iface?結構體的全貌:

iface 結構體全景

接著來看一下?eface?的源碼:

type eface struct {_type *_typedata unsafe.Pointer }

相比?iface,eface?就比較簡單了。只維護了一個?_type?字段,表示空接口所承載的具體的實體類型。data?描述了具體的值。

eface 結構體全景

我們來看個例子:

package mainimport "fmt"func main() {x := 200var any interface{} = xfmt.Println(any)g := Gopher{"Go"}var c coder = gfmt.Println(c) }type coder interface {code()debug() }type Gopher struct {language string }func (p Gopher) code() {fmt.Printf("I am coding %s language\n", p.language) }func (p Gopher) debug() {fmt.Printf("I am debuging %s language\n", p.language) }

執行命令,打印出匯編語言:

go tool compile -S ./src/main.go

可以看到,main 函數里調用了兩個函數:

func convT2E64(t *_type, elem unsafe.Pointer) (e eface) func convT2I(tab *itab, elem unsafe.Pointer) (i iface)

上面兩個函數的參數和?iface?及?eface?結構體的字段是可以聯系起來的:兩個函數都是將參數組裝一下,形成最終的接口。

作為補充,我們最后再來看下?_type?結構體:

type _type struct {// 類型大小size uintptrptrdata uintptr// 類型的 hash 值hash uint32// 類型的 flag,和反射相關tflag tflag// 內存對齊相關align uint8fieldalign uint8// 類型的編號,有bool, slice, struct 等等等等kind uint8alg *typeAlg// gc 相關gcdata *bytestr nameOffptrToThis typeOff }

Go 語言各種數據類型都是在?_type?字段的基礎上,增加一些額外的字段來進行管理的:

type arraytype struct {typ _typeelem *_typeslice *_typelen uintptr }type chantype struct {typ _typeelem *_typedir uintptr }type slicetype struct {typ _typeelem *_type }type structtype struct {typ _typepkgPath namefields []structfield }

這些數據類型的結構體定義,是反射實現的基礎。

4. 接口的動態類型和動態值

從源碼里可以看到:iface包含兩個字段:tab?是接口表指針,指向類型信息;data?是數據指針,則指向具體的數據。它們分別被稱為動態類型和動態值。而接口值包括動態類型和動態值。

【引申1】接口類型和?nil?作比較

接口值的零值是指動態類型和動態值都為?nil。當僅且當這兩部分的值都為?nil?的情況下,這個接口值就才會被認為?接口值 == nil。

來看個例子:

package mainimport "fmt"type Coder interface {code() }type Gopher struct {name string }func (g Gopher) code() {fmt.Printf("%s is coding\n", g.name) }func main() {var c Coderfmt.Println(c == nil)fmt.Printf("c: %T, %v\n", c, c)var g *Gopherfmt.Println(g == nil)c = gfmt.Println(c == nil)fmt.Printf("c: %T, %v\n", c, c) }

輸出:

true c: <nil>, <nil> true false c: *main.Gopher, <nil>

一開始,c?的 動態類型和動態值都為?nil,g?也為?nil,當把?g?賦值給?c?后,c?的動態類型變成了?*main.Gopher,僅管?c?的動態值仍為?nil,但是當?c?和?nil?作比較的時候,結果就是?false?了。

【引申2】
來看一個例子,看一下它的輸出:

package mainimport "fmt"type MyError struct {}func (i MyError) Error() string {return "MyError" }func main() {err := Process()fmt.Println(err)fmt.Println(err == nil) }func Process() error {var err *MyError = nilreturn err }

函數運行結果:

<nil> false

這里先定義了一個?MyError?結構體,實現了?Error?函數,也就實現了?error?接口。Process?函數返回了一個?error?接口,這塊隱含了類型轉換。所以,雖然它的值是?nil,其實它的類型是?*MyError,最后和?nil?比較的時候,結果為?false。

【引申3】如何打印出接口的動態類型和值?

直接看代碼:

package mainimport ("unsafe""fmt" )type iface struct {itab, data uintptr }func main() {var a interface{} = nilvar b interface{} = (*int)(nil)x := 5var c interface{} = (*int)(&x)ia := *(*iface)(unsafe.Pointer(&a))ib := *(*iface)(unsafe.Pointer(&b))ic := *(*iface)(unsafe.Pointer(&c))fmt.Println(ia, ib, ic)fmt.Println(*(*int)(unsafe.Pointer(ic.data))) }

代碼里直接定義了一個?iface?結構體,用兩個指針來描述?itab?和?data,之后將 a, b, c 在內存中的內容強制解釋成我們自定義的?iface。最后就可以打印出動態類型和動態值的地址。

運行結果如下:

{0 0} {17426912 0} {17426912 842350714568} 5

a 的動態類型和動態值的地址均為 0,也就是 nil;b 的動態類型和 c 的動態類型一致,都是?*int;最后,c 的動態值為 5。

5. 編譯器自動檢測類型是否實現接口

經常看到一些開源庫里會有一些類似下面這種奇怪的用法:

var _ io.Writer = (*myWriter)(nil)

這時候會有點懵,不知道作者想要干什么,實際上這就是此問題的答案。編譯器會由此檢查?*myWriter?類型是否實現了?io.Writer?接口。

來看一個例子:

package mainimport "io"type myWriter struct {}/*func (w myWriter) Write(p []byte) (n int, err error) {return }*/func main() {// 檢查 *myWriter 類型是否實現了 io.Writer 接口var _ io.Writer = (*myWriter)(nil)// 檢查 myWriter 類型是否實現了 io.Writer 接口var _ io.Writer = myWriter{} }

注釋掉為 myWriter 定義的 Write 函數后,運行程序:

src/main.go:14:6: cannot use (*myWriter)(nil) (type *myWriter) as type io.Writer in assignment:*myWriter does not implement io.Writer (missing Write method) src/main.go:15:6: cannot use myWriter literal (type myWriter) as type io.Writer in assignment:myWriter does not implement io.Writer (missing Write method)

報錯信息:*myWriter/myWriter 未實現 io.Writer 接口,也就是未實現 Write 方法。

解除注釋后,運行程序不報錯。

實際上,上述賦值語句會發生隱式地類型轉換,在轉換的過程中,編譯器會檢測等號右邊的類型是否實現了等號左邊接口所規定的函數。

總結一下,可通過在代碼中添加類似如下的代碼,用來檢測類型是否實現了接口:

var _ io.Writer = (*myWriter)(nil) var _ io.Writer = myWriter{}

6. 接口的構造過程是怎樣的

我們已經看過了?iface?和?eface?的源碼,知道?iface?最重要的是?itab?和?_type。

為了研究清楚接口是如何構造的,接下來我會拿起匯編的武器,還原背后的真相。

來看一個示例代碼:

package mainimport "fmt"type Person interface {growUp() }type Student struct {age int }func (p Student) growUp() {p.age += 1return }func main() {var qcrao = Person(Student{age: 18})fmt.Println(qcrao) }

執行命令:

go tool compile -S main.go

得到 main 函數的匯編代碼如下:

0x0000 00000 (./src/main.go:30) TEXT "".main(SB), $80-0 0x0000 00000 (./src/main.go:30) MOVQ (TLS), CX 0x0009 00009 (./src/main.go:30) CMPQ SP, 16(CX) 0x000d 00013 (./src/main.go:30) JLS 157 0x0013 00019 (./src/main.go:30) SUBQ $80, SP 0x0017 00023 (./src/main.go:30) MOVQ BP, 72(SP) 0x001c 00028 (./src/main.go:30) LEAQ 72(SP), BP 0x0021 00033 (./src/main.go:30) FUNCDATA$0, gclocals·69c1753bd5f81501d95132d08af04464(SB) 0x0021 00033 (./src/main.go:30) FUNCDATA$1, gclocals·e226d4ae4a7cad8835311c6a4683c14f(SB) 0x0021 00033 (./src/main.go:31) MOVQ $18, ""..autotmp_1+48(SP) 0x002a 00042 (./src/main.go:31) LEAQ go.itab."".Student,"".Person(SB), AX 0x0031 00049 (./src/main.go:31) MOVQ AX, (SP) 0x0035 00053 (./src/main.go:31) LEAQ ""..autotmp_1+48(SP), AX 0x003a 00058 (./src/main.go:31) MOVQ AX, 8(SP) 0x003f 00063 (./src/main.go:31) PCDATA $0, $0 0x003f 00063 (./src/main.go:31) CALL runtime.convT2I64(SB) 0x0044 00068 (./src/main.go:31) MOVQ 24(SP), AX 0x0049 00073 (./src/main.go:31) MOVQ 16(SP), CX 0x004e 00078 (./src/main.go:33) TESTQ CX, CX 0x0051 00081 (./src/main.go:33) JEQ 87 0x0053 00083 (./src/main.go:33) MOVQ 8(CX), CX 0x0057 00087 (./src/main.go:33) MOVQ $0, ""..autotmp_2+56(SP) 0x0060 00096 (./src/main.go:33) MOVQ $0, ""..autotmp_2+64(SP) 0x0069 00105 (./src/main.go:33) MOVQ CX, ""..autotmp_2+56(SP) 0x006e 00110 (./src/main.go:33) MOVQ AX, ""..autotmp_2+64(SP) 0x0073 00115 (./src/main.go:33) LEAQ ""..autotmp_2+56(SP), AX 0x0078 00120 (./src/main.go:33) MOVQ AX, (SP) 0x007c 00124 (./src/main.go:33) MOVQ $1, 8(SP) 0x0085 00133 (./src/main.go:33) MOVQ $1, 16(SP) 0x008e 00142 (./src/main.go:33) PCDATA $0, $1 0x008e 00142 (./src/main.go:33) CALL fmt.Println(SB) 0x0093 00147 (./src/main.go:34) MOVQ 72(SP), BP 0x0098 00152 (./src/main.go:34) ADDQ $80, SP 0x009c 00156 (./src/main.go:34) RET 0x009d 00157 (./src/main.go:34) NOP 0x009d 00157 (./src/main.go:30) PCDATA $0, $-1 0x009d 00157 (./src/main.go:30) CALL runtime.morestack_noctxt(SB) 0x00a2 00162 (./src/main.go:30) JMP 0

我們從第 10 行開始看,如果不理解前面幾行匯編代碼的話,可以回去看看公眾號前面兩篇文章,這里我就省略了。

匯編行數操作
10-14構造調用?runtime.convT2I64(SB)?的參數

我們來看下這個函數的參數形式:

func convT2I64(tab *itab, elem unsafe.Pointer) (i iface) {// …… }

convT2I64?會構造出一個?inteface,也就是我們的?Person?接口。

第一個參數的位置是?(SP),這里被賦上了?go.itab."".Student,"".Person(SB)?的地址。

我們從生成的匯編找到:

go.itab."".Student,"".Person SNOPTRDATA dupok size=400x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0010 00 00 00 00 00 00 00 00 da 9f 20 d4 rel 0+8 t=1 type."".Person+0rel 8+8 t=1 type."".Student+0

size=40?大小為40字節,回顧一下:

type itab struct {inter *interfacetype // 8字節_type *_type // 8字節link *itab // 8字節hash uint32 // 4字節bad bool // 1字節inhash bool // 1字節unused [2]byte // 2字節fun [1]uintptr // variable sized // 8字節 }

把每個字段的大小相加,itab?結構體的大小就是 40 字節。上面那一串數字實際上是?itab?序列化后的內容,注意到大部分數字是 0,從 24 字節開始的 4 個字節?da 9f 20 d4?實際上是?itab?的?hash?值,這在判斷兩個類型是否相同的時候會用到。

下面兩行是鏈接指令,簡單說就是將所有源文件綜合起來,給每個符號賦予一個全局的位置值。這里的意思也比較明確:前8個字節最終存儲的是?type."".Person?的地址,對應?itab?里的?inter?字段,表示接口類型;8-16 字節最終存儲的是?type."".Student?的地址,對應?itab?里?_type?字段,表示具體類型。

第二個參數就比較簡單了,它就是數字?18?的地址,這也是初始化?Student?結構體的時候會用到。

匯編行數操作
15調用?runtime.convT2I64(SB)

具體看下代碼:

func convT2I64(tab *itab, elem unsafe.Pointer) (i iface) {t := tab._type//...var x unsafe.Pointerif *(*uint64)(elem) == 0 {x = unsafe.Pointer(&zeroVal[0])} else {x = mallocgc(8, t, false)*(*uint64)(x) = *(*uint64)(elem)}i.tab = tabi.data = xreturn }

這塊代碼比較簡單,把?tab?賦給了?iface?的?tab?字段;data?部分則是在堆上申請了一塊內存,然后將?elem?指向的?18?拷貝過去。這樣?iface?就組裝好了。

匯編行數操作
17把?i.tab?賦給?CX
18把?i.data?賦給?AX
19-21檢測?i.tab?是否是 nil,如果不是的話,把 CX 移動 8 個字節,也就是把?itab?的?_type?字段賦給了 CX,這也是接口的實體類型,最終要作為?fmt.Println?函數的參數

后面,就是調用?fmt.Println?函數及之前的參數準備工作了,不再贅述。

這樣,我們就把一個?interface?的構造過程說完了。

【引申1】
如何打印出接口類型的?Hash?值?

這里參考曹大神翻譯的一篇文章,參考資料里會寫上。具體做法如下:

type iface struct {tab *itabdata unsafe.Pointer } type itab struct {inter uintptr_type uintptrlink uintptrhash uint32_ [4]bytefun [1]uintptr }func main() {var qcrao = Person(Student{age: 18})iface := (*iface)(unsafe.Pointer(&qcrao))fmt.Printf("iface.tab.hash = %#x\n", iface.tab.hash) }

定義了一個山寨版的?iface?和?itab,說它山寨是因為?itab?里的一些關鍵數據結構都不具體展開了,比如?_type,對比一下正宗的定義就可以發現,但是山寨版依然能工作,因為?_type?就是一個指針而已嘛。

在?main?函數里,先構造出一個接口對象?qcrao,然后強制類型轉換,最后讀取出?hash?值,非常妙!你也可以自己動手試一下。

運行結果:

iface.tab.hash = 0xd4209fda

值得一提的是,構造接口?qcrao?的時候,即使我把?age?寫成其他值,得到的?hash?值依然不變的,這應該是可以預料的,hash?值只和他的字段、方法相關。

7. 類型轉換和斷言的區別

我們知道,Go 語言中不允許隱式類型轉換,也就是說?=?兩邊,不允許出現類型不相同的變量。

類型轉換、類型斷言本質都是把一個類型轉換成另外一個類型。不同之處在于,類型斷言是對接口變量進行的操作。

類型轉換

對于類型轉換而言,轉換前后的兩個類型要相互兼容才行。類型轉換的語法為:

<結果類型> := <目標類型> ( <表達式> )

package mainimport "fmt"func main() {var i int = 9var f float64f = float64(i)fmt.Printf("%T, %v\n", f, f)f = 10.8a := int(f)fmt.Printf("%T, %v\n", a, a)// s := []int(i) }

上面的代碼里,我定義了一個?int?型和?float64?型的變量,嘗試在它們之前相互轉換,結果是成功的:int?型和?float64?是相互兼容的。

如果我把最后一行代碼的注釋去掉,編譯器會報告類型不兼容的錯誤:

cannot convert i (type int) to type []int

斷言

前面說過,因為空接口?interface{}?沒有定義任何函數,因此 Go 中所有類型都實現了空接口。當一個函數的形參是?interface{},那么在函數中,需要對形參進行斷言,從而得到它的真實類型。

斷言的語法為:

<目標類型的值>,<布爾參數> := <表達式>.( 目標類型 ) // 安全類型斷言
<目標類型的值> := <表達式>.( 目標類型 )  //非安全類型斷言

類型轉換和類型斷言有些相似,不同之處,在于類型斷言是對接口進行的操作。

還是來看一個簡短的例子:

package mainimport "fmt"type Student struct {Name stringAge int }func main() {var i interface{} = new(Student)s := i.(Student)fmt.Println(s) }

運行一下:

panic: interface conversion: interface {} is *main.Student, not main.Student

直接?panic?了,這是因為?i?是?*Student?類型,并非?Student?類型,斷言失敗。這里直接發生了?panic,線上代碼可能并不適合這樣做,可以采用“安全斷言”的語法:

func main() {var i interface{} = new(Student)s, ok := i.(Student)if ok {fmt.Println(s)} }

這樣,即使斷言失敗也不會?panic。

斷言其實還有另一種形式,就是用在利用?switch?語句判斷接口的類型。每一個?case?會被順序地考慮。當命中一個?case?時,就會執行?case?中的語句,因此?case?語句的順序是很重要的,因為很有可能會有多個?case?匹配的情況。

代碼示例如下:

func main() {//var i interface{} = new(Student)//var i interface{} = (*Student)(nil)var i interface{}fmt.Printf("%p %v\n", &i, i)judge(i) }func judge(v interface{}) {fmt.Printf("%p %v\n", &v, v)switch v := v.(type) {case nil:fmt.Printf("%p %v\n", &v, v)fmt.Printf("nil type[%T] %v\n", v, v)case Student:fmt.Printf("%p %v\n", &v, v)fmt.Printf("Student type[%T] %v\n", v, v)case *Student:fmt.Printf("%p %v\n", &v, v)fmt.Printf("*Student type[%T] %v\n", v, v)default:fmt.Printf("%p %v\n", &v, v)fmt.Printf("unknow\n")} }type Student struct {Name stringAge int }

main?函數里有三行不同的聲明,每次運行一行,注釋另外兩行,得到三組運行結果:

// --- var i interface{} = new(Student) 0xc4200701b0 [Name: ], [Age: 0] 0xc4200701d0 [Name: ], [Age: 0] 0xc420080020 [Name: ], [Age: 0] *Student type[*main.Student] [Name: ], [Age: 0]// --- var i interface{} = (*Student)(nil) 0xc42000e1d0 <nil> 0xc42000e1f0 <nil> 0xc42000c030 <nil> *Student type[*main.Student] <nil>// --- var i interface{} 0xc42000e1d0 <nil> 0xc42000e1e0 <nil> 0xc42000e1f0 <nil> nil type[<nil>] <nil>

對于第一行語句:

var i interface{} = new(Student)

i?是一個?*Student?類型,匹配上第三個 case,從打印的三個地址來看,這三處的變量實際上都是不一樣的。在?main?函數里有一個局部變量?i;調用函數時,實際上是復制了一份參數,因此函數里又有一個變量?v,它是?i?的拷貝;斷言之后,又生成了一份新的拷貝。所以最終打印的三個變量的地址都不一樣。

對于第二行語句:

var i interface{} = (*Student)(nil)

這里想說明的其實是?i?在這里動態類型是?(*Student), 數據為?nil,它的類型并不是?nil,它與?nil?作比較的時候,得到的結果也是?false。

最后一行語句:

var i interface{}

這回?i?才是?nil?類型。

【引申1】
fmt.Println?函數的參數是?interface。對于內置類型,函數內部會用窮舉法,得出它的真實類型,然后轉換為字符串打印。而對于自定義類型,首先確定該類型是否實現了?String()?方法,如果實現了,則直接打印輸出?String()?方法的結果;否則,會通過反射來遍歷對象的成員進行打印。

再來看一個簡短的例子,比較簡單,不要緊張:

package mainimport "fmt"type Student struct {Name stringAge int }func main() {var s = Student{Name: "qcrao",Age: 18,}fmt.Println(s) }

因為?Student?結構體沒有實現?String()?方法,所以?fmt.Println?會利用反射挨個打印成員變量:

{qcrao 18}

增加一個?String()?方法的實現:

func (s Student) String() string {return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age) }

打印結果:

[Name: qcrao], [Age: 18]

按照我們自定義的方法來打印了。

【引申2】
針對上面的例子,如果改一下:

func (s *Student) String() string {return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age) }

注意看兩個函數的接受者類型不同,現在?Student?結構體只有一個接受者類型為?指針類型?的?String()?函數,打印結果:

{qcrao 18}

為什么?

類型?T?只有接受者是?T?的方法;而類型?*T?擁有接受者是?T?和?*T?的方法。語法上?T?能直接調?*T?的方法僅僅是?Go?的語法糖。

所以,?Student?結構體定義了接受者類型是值類型的?String()?方法時,通過

fmt.Println(s) fmt.Println(&s)

均可以按照自定義的格式來打印。

如果?Student?結構體定義了接受者類型是指針類型的?String()?方法時,只有通過

fmt.Println(&s)

才能按照自定義的格式打印。

8. 接口轉換的原理

通過前面提到的?iface?的源碼可以看到,實際上它包含接口的類型?interfacetype?和 實體類型的類型?_type,這兩者都是?iface?的字段?itab?的成員。也就是說生成一個?itab?同時需要接口的類型和實體的類型。

<interface 類型, 實體類型> ->itable

當判定一種類型是否滿足某個接口時,Go 使用類型的方法集和接口所需要的方法集進行匹配,如果類型的方法集完全包含接口的方法集,則可認為該類型實現了該接口。

例如某類型有?m?個方法,某接口有?n?個方法,則很容易知道這種判定的時間復雜度為?O(mn),Go 會對方法集的函數按照函數名的字典序進行排序,所以實際的時間復雜度為?O(m+n)。

這里我們來探索將一個接口轉換給另外一個接口背后的原理,當然,能轉換的原因必然是類型兼容。

直接來看一個例子:

package mainimport "fmt"type coder interface {code()run() }type runner interface {run() }type Gopher struct {language string }func (g Gopher) code() {return }func (g Gopher) run() {return }func main() {var c coder = Gopher{}var r runnerr = cfmt.Println(c, r) }

簡單解釋下上述代碼:定義了兩個?interface:?coder?和?runner。定義了一個實體類型?Gopher,類型?Gopher?實現了兩個方法,分別是?run()?和?code()。main 函數里定義了一個接口變量?c,綁定了一個?Gopher?對象,之后將?c?賦值給另外一個接口變量?r?。賦值成功的原因是?c?中包含?run()?方法。這樣,兩個接口變量完成了轉換。

執行命令:

go tool compile -S ./src/main.go

得到 main 函數的匯編命令,可以看到:?r = c?這一行語句實際上是調用了?runtime.convI2I(SB),也就是?convI2I?函數,從函數名來看,就是將一個?interface?轉換成另外一個?interface,看下它的源代碼:

func convI2I(inter *interfacetype, i iface) (r iface) {tab := i.tabif tab == nil {return}if tab.inter == inter {r.tab = tabr.data = i.datareturn}r.tab = getitab(inter, tab._type, false)r.data = i.datareturn }

代碼比較簡單,函數參數?inter?表示接口類型,i?表示綁定了實體類型的接口,r?則表示接口轉換了之后的新的?iface。通過前面的分析,我們又知道,?iface?是由?tab?和?data?兩個字段組成。所以,實際上?convI2I?函數真正要做的事,找到新?interface?的?tab?和?data,就大功告成了。

我們還知道,tab?是由接口類型?interfacetype?和 實體類型?_type?組成。所以最關鍵的語句是?r.tab = getitab(inter, tab._type, false)。

因此,重點來看下?getitab?函數的源碼,只看關鍵的地方:

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {// ……// 根據 inter, typ 計算出 hash 值h := itabhash(inter, typ)// look twice - once without lock, once with.// common case will be no lock contention.var m *itabvar locked intfor locked = 0; locked < 2; locked++ {if locked != 0 {lock(&ifaceLock)}// 遍歷哈希表的一個 slotfor m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {// 如果在 hash 表中已經找到了 itab(inter 和 typ 指針都相同)if m.inter == inter && m._type == typ {// ……if locked != 0 {unlock(&ifaceLock)}return m}}}// 在 hash 表中沒有找到 itab,那么新生成一個 itabm = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))m.inter = interm._type = typ// 添加到全局的 hash 表中additab(m, true, canfail)unlock(&ifaceLock)if m.bad {return nil}return m }

簡單總結一下:getitab 函數會根據?interfacetype?和?_type?去全局的 itab 哈希表中查找,如果能找到,則直接返回;否則,會根據給定的?interfacetype?和?_type?新生成一個?itab,并插入到 itab 哈希表,這樣下一次就可以直接拿到?itab。

這里查找了兩次,并且第二次上鎖了,這是因為如果第一次沒找到,在第二次仍然沒有找到相應的?itab?的情況下,需要新生成一個,并且寫入哈希表,因此需要加鎖。這樣,其他協程在查找相同的?itab?并且也沒有找到時,第二次查找時,會被掛住,之后,就會查到第一個協程寫入哈希表的?itab。

再來看一下?additab?函數的代碼:

// 檢查 _type 是否符合 interface_type 并且創建對應的 itab 結構體 將其放到 hash 表中 func additab(m *itab, locked, canfail bool) {inter := m.intertyp := m._typex := typ.uncommon()// both inter and typ have method sorted by name,// and interface names are unique,// so can iterate over both in lock step;// the loop is O(ni+nt) not O(ni*nt).// // inter 和 typ 的方法都按方法名稱進行了排序// 并且方法名都是唯一的。所以循環的次數是固定的// 只用循環 O(ni+nt),而非 O(ni*nt)ni := len(inter.mhdr)nt := int(x.mcount)xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]j := 0for k := 0; k < ni; k++ {i := &inter.mhdr[k]itype := inter.typ.typeOff(i.ityp)name := inter.typ.nameOff(i.name)iname := name.name()ipkg := name.pkgPath()if ipkg == "" {ipkg = inter.pkgpath.name()}for ; j < nt; j++ {t := &xmhdr[j]tname := typ.nameOff(t.name)// 檢查方法名字是否一致if typ.typeOff(t.mtyp) == itype && tname.name() == iname {pkgPath := tname.pkgPath()if pkgPath == "" {pkgPath = typ.nameOff(x.pkgpath).name()}if tname.isExported() || pkgPath == ipkg {if m != nil {// 獲取函數地址,并加入到itab.fun數組中ifn := typ.textOff(t.ifn)*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn}goto nextimethod}}}// ……m.bad = truebreaknextimethod:}if !locked {throw("invalid itab locking")}// 計算 hash 值h := itabhash(inter, typ)// 加到Hash Slot鏈表中m.link = hash[h]m.inhash = trueatomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m)) }

additab?會檢查?itab?持有的?interfacetype?和?_type?是否符合,就是看?_type?是否完全實現了?interfacetype?的方法,也就是看兩者的方法列表重疊的部分就是?interfacetype?所持有的方法列表。注意到其中有一個雙層循環,乍一看,循環次數是?ni * nt,但由于兩者的函數列表都按照函數名稱進行了排序,因此最終只執行了?ni + nt?次,代碼里通過一個小技巧來實現:第二層循環并沒有從 0 開始計數,而是從上一次遍歷到的位置開始。

求 hash 值的函數比較簡單:

func itabhash(inter *interfacetype, typ *_type) uint32 {h := inter.typ.hashh += 17 * typ.hashreturn h % hashSize }

hashSize?的值是 1009。

更一般的,當把實體類型賦值給接口的時候,會調用?conv?系列函數,例如空接口調用?convT2E?系列、非空接口調用?convT2I?系列。這些函數比較相似:

  • 具體類型轉空接口時,_type 字段直接復制源類型的 _type;調用 mallocgc 獲得一塊新內存,把值復制進去,data 再指向這塊新內存。
  • 具體類型轉非空接口時,入參 tab 是編譯器在編譯階段預先生成好的,新接口 tab 字段直接指向入參 tab 指向的 itab;調用 mallocgc 獲得一塊新內存,把值復制進去,data 再指向這塊新內存。
  • 而對于接口轉接口,itab 調用 getitab 函數獲取。只用生成一次,之后直接從 hash 表中獲取。
  • 9. 如何用 interface 實現多態

    Go?語言并沒有設計諸如虛函數、純虛函數、繼承、多重繼承等概念,但它通過接口卻非常優雅地支持了面向對象的特性。

    多態是一種運行期的行為,它有以下幾個特點:

  • 一種類型具有多種類型的能力
  • 允許不同的對象對同一消息做出靈活的反應
  • 以一種通用的方式對待使用的對象
  • 非動態語言必須通過繼承和接口的方式來實現
  • 看一個實現了多態的代碼例子:

    package mainimport "fmt"func main() {qcrao := Student{age: 18}whatJob(&qcrao)growUp(&qcrao)fmt.Println(qcrao)stefno := Programmer{age: 100}whatJob(stefno)growUp(stefno)fmt.Println(stefno) }func whatJob(p Person) {p.job() }func growUp(p Person) {p.growUp() }type Person interface {job()growUp() }type Student struct {age int }func (p Student) job() {fmt.Println("I am a student.")return }func (p *Student) growUp() {p.age += 1return }type Programmer struct {age int }func (p Programmer) job() {fmt.Println("I am a programmer.")return }func (p Programmer) growUp() {// 程序員老得太快 ^_^p.age += 10return }

    代碼里先定義了 1 個?Person?接口,包含兩個函數:

    job() growUp()

    然后,又定義了 2 個結構體,Student?和?Programmer,同時,類型?*Student、Programmer?實現了?Person?接口定義的兩個函數。注意,*Student?類型實現了接口,?Student?類型卻沒有。

    之后,我又定義了函數參數是?Person?接口的兩個函數:

    func whatJob(p Person) func growUp(p Person)

    main?函數里先生成?Student?和?Programmer?的對象,再將它們分別傳入到函數?whatJob?和?growUp。函數中,直接調用接口函數,實際執行的時候是看最終傳入的實體類型是什么,調用的是實體類型實現的函數。于是,不同對象針對同一消息就有多種表現,多態就實現了。

    更深入一點來說的話,在函數?whatJob()?或者?growUp()?內部,接口?person?綁定了實體類型?*Student?或者?Programmer。根據前面分析的?iface?源碼,這里會直接調用?fun?里保存的函數,類似于:?s.tab->fun[0],而因為?fun?數組里保存的是實體類型實現的函數,所以當函數傳入不同的實體類型時,調用的實際上是不同的函數實現,從而實現多態。

    運行一下代碼:

    I am a student. {19} I am a programmer. {100}

    10. Go 接口與 C++ 接口有何異同

    接口定義了一種規范,描述了類的行為和功能,而不做具體實現。

    C++ 的接口是使用抽象類來實現的,如果類中至少有一個函數被聲明為純虛函數,則這個類就是抽象類。純虛函數是通過在聲明中使用 “= 0” 來指定的。例如:

    class Shape {public:// 純虛函數virtual double getArea() = 0;private:string name; // 名稱 };

    設計抽象類的目的,是為了給其他類提供一個可以繼承的適當的基類。抽象類不能被用于實例化對象,它只能作為接口使用。

    派生類需要明確地聲明它繼承自基類,并且需要實現基類中所有的純虛函數。

    C++ 定義接口的方式稱為“侵入式”,而 Go 采用的是 “非侵入式”,不需要顯式聲明,只需要實現接口定義的函數,編譯器自動會識別。

    C++ 和 Go 在定義接口方式上的不同,也導致了底層實現上的不同。C++ 通過虛函數表來實現基類調用派生類的函數;而 Go 通過?itab?中的?fun?字段來實現接口變量調用實體類型的函數。C++ 中的虛函數表是在編譯期生成的;而 Go 的?itab?中的?fun?字段是在運行期間動態生成的。原因在于,Go 中實體類型可能會無意中實現 N 多接口,很多接口并不是本來需要的,所以不能為類型實現的所有接口都生成一個?itab, 這也是“非侵入式”帶來的影響;這在 C++ 中是不存在的,因為派生需要顯示聲明它繼承自哪個基類。

    總結

    以上是生活随笔為你收集整理的golang 接口类型 interface 简介使用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    91av原创| wwwwwww色| 国产精品99爱 | 看av免费| 午夜色影院| 国产精品剧情 | 97视频免费在线 | 亚洲激情av| 亚洲国产中文字幕在线 | 六月色丁香 | 黄色在线看网站 | 97人人模人人爽人人喊网 | 国产视频欧美视频 | 色精品视频| 一级c片 | 丁香激情网 | 中文字幕一区二区三区四区久久 | 精品免费久久久久 | 亚洲成人黄色av | 国产亚洲精品日韩在线tv黄 | 亚洲精品伦理在线 | 欧美做受高潮电影o | 精品少妇一区二区三区在线 | 精品美女在线观看 | 国内视频一区二区 | 99久久精品国产系列 | 草久久影院 | 亚洲精品视频久久 | 韩国视频一区二区三区 | 狠狠天天 | 91探花系列在线播放 | 色视频在线观看 | 免费色网| 天堂av中文字幕 | 亚洲作爱 | 国产最新精品视频 | 久久激情小说 | 亚洲综合丁香 | 视频国产区| 欧美va天堂在线电影 | 国产亚洲精品精品精品 | 91免费高清 | 亚洲成av人片在线观看香蕉 | 日韩精品视频网站 | 欧美少妇xx | 黄色网中文字幕 | 国产精品久久久久三级 | 在线免费黄色av | 久操免费视频 | 成人欧美一区二区三区在线观看 | 久久精品一区八戒影视 | 亚洲欧美久久 | 丁香花在线视频观看免费 | 欧洲精品视频一区二区 | 99精品视频在线播放观看 | 四虎影视精品永久在线观看 | 久久精品久久精品 | 97免费在线观看 | 99久久精品国产免费看不卡 | 久久国产午夜精品理论片最新版本 | 日韩久久视频 | 涩av在线 | 97超碰成人在线 | 中文在线字幕免费观 | 三级小视频在线观看 | 免费看精品久久片 | 午夜精品久久久久久中宇69 | 亚洲人成影院在线 | 国产高清视频免费 | 99产精品成人啪免费网站 | 日韩精品一区电影 | 肉色欧美久久久久久久免费看 | 色就色,综合激情 | 欧美色黄| 国产不卡在线观看视频 | 香蕉视频国产在线观看 | 视频二区| 欧美一级视频免费看 | 久久伦理影院 | 久一网站| 在线观看麻豆av | 久久成年人网站 | 欧美国产日韩在线观看 | 99色资源| 九九爱免费视频 | 韩日视频在线 | 久草免费在线观看视频 | 中文字幕日韩一区二区三区不卡 | 色综合久久综合中文综合网 | 久久综合亚洲鲁鲁五月久久 | 日韩精品一区二区三区视频播放 | 99精品免费久久久久久久久日本 | 黄色片软件网站 | 99视频精品视频高清免费 | 国产成人精品一区二区三区 | 91免费在线看片 | 天海冀一区二区三区 | 超薄丝袜一二三区 | 丁香色综合 | 国产91综合一区在线观看 | 久99久在线 | 色a资源在线 | 午夜性盈盈 | 91精品欧美一区二区三区 | 777视频在线观看 | 毛片网站在线 | 成人精品影视 | 在线视频一区二区 | 国产精品破处视频 | 久久亚洲热 | 国产精品亚洲a | 日韩在线精品 | 国产大尺度视频 | 九九免费在线观看 | 99久久999久久久精玫瑰 | 夜添久久精品亚洲国产精品 | 国产精品久久久毛片 | 欧美成人aa| 欧美小视频在线观看 | 国产成人精品av在线 | 久久久久久高清 | 在线日韩中文字幕 | 一 级 黄 色 片免费看的 | 四虎成人精品永久免费av九九 | 午夜 免费 | 91色一区二区三区 | 五月天亚洲婷婷 | 黄色免费高清视频 | 91成熟丰满女人少妇 | 亚洲第一区精品 | 欧美性网站 | av福利网址导航大全 | 日韩视频欧美视频 | 日韩在线免费小视频 | 狠狠成人| 亚洲精品久久久久999中文字幕 | 91香蕉视频 mp4 | 日韩欧美成人网 | 97视频资源| av中文天堂 | 91精品国产99久久久久久红楼 | 亚洲资源在线观看 | 亚州欧美视频 | 91精品视频在线免费观看 | 精品福利国产 | 欧美福利视频 | 精品国产欧美一区二区 | 色综合婷婷久久 | 久草在线最新视频 | 91精品国产福利在线观看 | 国产精品自产拍在线观看网站 | 久久免费a| 人人看黄色 | 午夜视频在线瓜伦 | 亚洲国产精品电影在线观看 | 国产人在线成免费视频 | 中文字幕婷婷 | 天天综合网 天天综合色 | 亚洲精品免费在线视频 | av免费网站在线观看 | 中文字幕欧美日韩va免费视频 | 中文字幕a在线 | www.国产毛片 | 亚洲理论电影网 | 99视频在线精品免费观看2 | 成人在线你懂得 | 国产不卡毛片 | 视频国产一区二区三区 | 亚洲伦理一区二区 | 中文字幕在线观看av | 欧美aaa一级 | 韩国精品一区二区三区六区色诱 | a在线观看视频 | 久草在线综合网 | 色999在线| 亚洲精品动漫在线 | 日韩欧美在线视频一区二区 | 欧美精品三级 | 天天干夜夜想 | 国产喷水在线 | 国产毛片久久久 | 97色综合| 在线v片免费观看视频 | 亚洲在线精品 | 97精品久久人人爽人人爽 | 91最新视频 | 日韩欧美在线视频一区二区三区 | 91香蕉视频在线 | 国产一级在线播放 | 成人av网页 | 岛国av在线不卡 | 男女视频久久久 | 婷婷5月色 | 免费在线观看午夜视频 | 黄色成人在线网站 | 亚洲一区视频免费观看 | 日本亚洲国产 | 欧美国产日韩在线视频 | 精品国产成人在线 | 久久成人午夜视频 | 97超碰在| 99久热在线精品视频成人一区 | 人人爱人人做人人爽 | 亚洲国产理论片 | 高清av免费看 | 激情小说久久 | 日韩在线视频看看 | 色噜噜噜噜| 欧美另类交在线观看 | 国产精品18久久久 | 精品超碰 | 久久免费视频这里只有精品 | 亚洲精品美女在线观看播放 | 黄色综合| 97超碰国产精品 | 极品美女被弄高潮视频网站 | 婷婷色中文 | 欧美精品久久久久久久久老牛影院 | 西西444www高清大胆 | 久久精品国产免费看久久精品 | 亚洲dvd | 国产成人精品久久久久 | 日本中文乱码卡一卡二新区 | 天天爱天天舔 | 欧美日韩精品在线观看 | 视频直播国产精品 | 青青草国产免费 | 在线观看国产亚洲 | 久草爱视频 | 少妇高潮冒白浆 | 日韩精品免费在线视频 | 国产精品video爽爽爽爽 | 色婷婷综合久久久中文字幕 | 狠狠色噜噜狠狠狠狠 | 啪啪激情网 | 久久经典视频 | 欧美特一级 | 1024手机基地在线观看 | 亚洲国产大片 | 婷婷中文字幕 | 丁香婷婷基地 | 四虎www com| 狠狠躁夜夜a产精品视频 | 欧美污污视频 | 国产精品刺激对白麻豆99 | 国产一区二区三区免费观看视频 | 久久成人麻豆午夜电影 | 国内精品久久久久 | 免费高清在线视频一区· | 9ⅰ精品久久久久久久久中文字幕 | 亚洲女同videos | 午夜精品久久一牛影视 | 国产综合福利在线 | 亚洲成人av一区二区 | 8090yy亚洲精品久久 | 国产精品a成v人在线播放 | 美女天天操 | 国产在线91精品 | 国产一级免费视频 | 99热这里只有精品免费 | 久久视频精品在线观看 | 波多野结衣电影一区二区三区 | 国产一级二级视频 | 成人在线免费视频观看 | 国产高清在线观看 | 日日干天天插 | 婷婷六月在线 | 国产一级淫片在线观看 | 三级免费黄色 | 美女黄频网站 | 欧美孕妇视频 | 午夜私人影院久久久久 | 午夜精品视频免费在线观看 | 五月天婷婷综合 | 欧美日韩另类在线观看 | 日韩高清国产精品 | 一区二区视频电影在线观看 | 91tv国产成人福利 | 久久99热这里只有精品国产 | 午夜 久久 tv | 日韩精品免费专区 | 国产麻豆传媒 | 日韩欧美在线一区二区 | 99在线精品免费视频九九视 | 天堂成人在线 | 亚洲影院色 | 人人精品| 日日噜噜噜噜夜夜爽亚洲精品 | 成人丝袜 | 一区二区三区免费播放 | www.888.av| 久久国产精品久久w女人spa | 国产精品久久久久久久av大片 | 日韩av一区在线观看 | 国产成人无码AⅤ片在线观 日韩av不卡在线 | 午夜久操 | 91黄站| 亚洲欧美怡红院 | 亚洲日本va午夜在线电影 | 激情伊人 | 婷婷伊人综合亚洲综合网 | 四虎在线免费视频 | 久久精品播放 | 免费午夜在线视频 | 亚洲午夜久久久久久久久 | 国产伦理一区 | 欧美日在线 | 日韩欧美一区视频 | 欧美日韩性视频 | 久久精品五月 | 黄色软件网站在线观看 | www日韩视频 | 日本精品中文字幕在线观看 | 欧美综合色在线图区 | 亚洲精品毛片一级91精品 | 毛片的网址 | 久久精品播放 | 精品国产一区二区三区四 | 九九九热精品免费视频观看网站 | 91av大全 | 国产精品一区免费在线观看 | 激情九九 | 天天操狠狠操网站 | 亚洲一级二级三级 | 午夜婷婷网 | 日韩中文久久 | 午夜久久美女 | 欧美日韩精品免费观看视频 | 中文字幕日本电影 | 人人干人人干人人干 | 久草视频在线免费 | 一区二区中文字幕在线观看 | 欧美在线观看视频免费 | 99久久久国产精品美女 | 国产永久网站 | 免费看的黄色 | 黄色三级免费片 | 国内精品久久天天躁人人爽 | 国产精品第52页 | 六月久久婷婷 | 另类五月激情 | 天天艹| 99视频国产在线 | 免费又黄又爽的视频 | 91精品人成在线观看 | 日韩精品久久久久久中文字幕8 | 欧美日韩激情视频8区 | 久久久亚洲精品 | 国产在线v| 亚洲日本国产 | 最新在线你懂的 | 最近中文字幕免费大全 | 奇米网在线观看 | 999国产| 久久五月婷婷丁香社区 | 日本护士三级少妇三级999 | 99久久夜色精品国产亚洲96 | 国产无遮挡猛进猛出免费软件 | 中文在线a√在线 | 97人人模人人爽人人喊网 | 美女久久| 国产精品免费不卡 | 国产视频1区2区 | 国产精品乱码久久 | 精品久久久精品 | 在线免费观看视频a | 成人免费观看av | 人人爽人人爽人人 | 婷婷午夜 | 国产色网 | 婷婷干五月 | 日日夜夜婷婷 | 久久av影视 | 免费在线视频一区二区 | 四虎在线永久免费观看 | 伊人影院在线观看 | 91色吧 | 国产九九九视频 | a视频免费 | 91久久精品一区二区二区 | 夜夜躁日日躁狠狠躁 | 亚洲精品观看 | www.操.com| 欧洲一区二区三区精品 | 国精产品999国精产品岳 | 久热电影 | 久久久久久中文字幕 | 在线国产黄色 | 四虎国产精品永久在线国在线 | 激情久久久久久久久久久久久久久久 | 99视频导航| 一区二区三区免费在线 | 色综合久久久久久久 | 亚洲欧美成人在线 | 九九精品视频在线 | 人人爽久久久噜噜噜电影 | 亚洲国产中文字幕在线观看 | 天天色天天操天天爽 | 午夜久久视频 | 二区在线播放 | 亚洲欧美怡红院 | 亚洲国产精品视频在线观看 | 欧美日韩一级久久久久久免费看 | 亚洲国产97在线精品一区 | 婷婷亚洲五月色综合 | 99热在线观看 | 日韩精品免费在线 | 国产亚洲片 | 久久久www成人免费毛片 | 中文字幕中文字幕 | 黄色www免费 | 久久电影色 | 久久久久亚洲精品成人网小说 | 欧美日韩中文在线视频 | 久久久久伊人 | 2021国产视频 | 日韩国产欧美在线播放 | 麻豆成人在线观看 | 在线播放国产精品 | 久久久www成人免费精品张筱雨 | 99精品免费久久久久久久久 | 91污污视频在线观看 | 五月婷婷欧美视频 | 天天射综合 | 国产一级免费片 | 中文字幕永久在线 | av在线进入 | 91九色蝌蚪国产 | 国产精品一区二区三区观看 | 欧美a级片网站 | 黄色国产精品 | 91视频链接 | 欧美在线18 | 日韩av手机在线观看 | 免费看国产一级片 | 欧美一级性生活视频 | 日本久久成人 | 亚洲成av | 97成人精品 | 欧美性成人| 日本中文一区二区 | 成年人在线观看视频免费 | 中文乱幕日产无线码1区 | av在线在线 | 国产一区欧美日韩 | 久久精品之 | 国产成人精品网站 | 亚洲最大成人免费网站 | 欧美极品一区二区三区 | 久久天 | 777久久久 | 国产精品久久久免费 | 国产综合片 | 成人亚洲免费 | 91看国产| 在线观看你懂的网址 | 91福利社区在线观看 | 国产高清精品在线 | 狠狠久久综合 | 天天干天天看 | 欧美 亚洲 另类 激情 另类 | 婷婷在线网 | 国内精品99| 久久久毛片 | 成人黄大片视频在线观看 | 日韩精品一区电影 | 久久精品国产一区二区 | 亚洲成人一区 | 8x成人免费视频 | 精品在线二区 | 国产亚洲精品久久久久动 | 成人a v视频 | 蜜臀av夜夜澡人人爽人人 | 中文字幕乱码日本亚洲一区二区 | 中文字幕视频三区 | 国产精美视频 | 成人性生交大片免费看中文网站 | 日韩av午夜 | 国产专区视频在线观看 | 毛片99 | 日本精品免费看 | 国产一线二线三线在线观看 | 美女视频a美女大全免费下载蜜臀 | 天天插夜夜操 | 成人超碰在线 | 欧美日韩xxxxx | 丁香 婷婷 激情 | 国产精品毛片久久 | 久久国内精品视频 | 亚洲精品av中文字幕在线在线 | 日韩网站中文字幕 | 久久午夜网 | 色之综合网 | 国产精品久久久久久麻豆一区 | 国产成人综合精品 | 中文字幕免费高清av | 国产一级片不卡 | 一区二区观看 | 国产一区二区在线影院 | 国产精品久久久久久久久久尿 | 久草在线看片 | 伊人中文在线 | 国产黄色片一级 | 色六月婷婷 | 日日夜夜天天 | 九草视频在线观看 | 成人久久免费视频 | 66av99精品福利视频在线 | 久久久久亚洲精品男人的天堂 | 9在线观看免费高清完整版在线观看明 | 在线视频日韩精品 | 在线观看午夜av | 91看片看淫黄大片 | 亚洲国产免费看 | 国产福利网站 | 国产午夜精品免费一区二区三区视频 | 麻豆视频在线看 | 日本成人免费在线观看 | 成人av在线一区二区 | 五月天电影免费在线观看一区 | 日韩精品在线视频免费观看 | 区一区二区三在线观看 | 91av视频观看 | 成人午夜网址 | 麻豆传媒在线免费看 | 草久在线观看视频 | 精品产品国产在线不卡 | 成人免费视频播放 | 午夜精品久久久久久久久久久久久久 | 久久精品久久精品久久39 | 久草电影在线观看 | 综合久久综合久久 | 亚洲天天摸日日摸天天欢 | www.com久久 | 欧美日韩国产色综合一二三四 | 久热只有精品 | 91一区啪爱嗯打偷拍欧美 | 免费看日韩片 | 免费在线电影网址大全 | 久久99九九99精品 | 黄色三级在线看 | 在线中文字幕视频 | 久久免费a | 精品欧美一区二区在线观看 | 超碰97国产在线 | 久久国产精品免费视频 | 四虎成人精品永久免费av九九 | 午夜精品久久久久久久久久久 | 国产成人精品一区一区一区 | 亚洲理论电影网 | 日韩大片在线免费观看 | 日韩欧美视频一区 | 337p欧美| 久久99亚洲精品久久久久 | 欧美精品亚洲精品日韩精品 | 国产色视频网站2 | 91久久精| 亚洲午夜精品久久久久久久久 | 亚洲三级在线 | 亚洲国产视频在线 | 欧美成人在线网站 | 免费在线观看一区二区三区 | 久久视频免费在线观看 | 欧美视频日韩 | 成年人在线观看免费视频 | 午夜丁香视频在线观看 | 精品国产三级a∨在线欧美 免费一级片在线观看 | 日产乱码一二三区别免费 | 日本电影久久 | 亚洲在线看| 国产一区二区在线看 | 国产精品伦一区二区三区视频 | 美女久久99 | 西西www4444大胆在线 | 三级毛片视频 | 国产九九热| 精品国产乱码久久久久久1区2匹 | 国产黄色播放 | 91麻豆精品国产自产在线游戏 | 亚洲午夜精品久久久久久久久 | 免费日韩电影 | 视频 国产区 | 最近免费中文字幕mv在线视频3 | 夜夜夜草 | av看片在线观看 | 久久香蕉影视 | 91亚洲在线 | 最新久久久 | 国产精品综合久久久久久 | 久精品在线 | 欧美一级片免费播放 | 国内精品在线观看视频 | 久久久久高清毛片一级 | 国产精品久久久久久模特 | 青草草在线 | 在线免费av网 | av中文字幕网| 成人a在线| 久草免费在线视频观看 | 午夜精品电影 | 最近中文字幕完整视频高清1 | 亚洲欧洲精品一区二区精品久久久 | 9999激情| 97在线观看免费高清完整版在线观看 | 麻豆久久久久久久 | 国产成人精品在线播放 | 手机av网站 | 久香蕉| 日韩一区二区久久 | 蜜臀久久99精品久久久酒店新书 | 国产精品麻豆三级一区视频 | 国产精品影音先锋 | 黄色一级性片 | 久操操| 99热播精品 | 深夜视频久久 | 亚洲在线不卡 | 国产精品资源 | 2020天天干夜夜爽 | 性色av一区二区三区在线观看 | 久草在线网址 | 97人人澡人人添人人爽超碰 | 成人免费电影 | www.夜色321.com| 国产小视频免费在线网址 | 97免费在线视频 | 午夜99| 欧美日在线观看 | 六月丁香社区 | 日日爱夜夜爱 | 色综合五月天 | 亚洲精品国产品国语在线 | www最近高清中文国语在线观看 | 国产精品久久9 | 天天操伊人 | 国产精品久久久久久久午夜 | 日韩r级电影在线观看 | 欧美日韩视频在线一区 | 国产激情免费 | 亚洲国产精品成人综合 | 亚洲激情网站免费观看 | 国产一区二区三区 在线 | 狠狠操导航 | 麻豆视频在线播放 | 欧美激情视频免费看 | 久久精品高清 | 干干干操操操 | 成人午夜影视 | 国产精品久久久久国产精品日日 | 精品视频免费久久久看 | 一区二区精品国产 | 国产精品久久久免费 | 国产一级二级av | 国产精品欧美久久久久三级 | 国产精久久久久久妇女av | 人人插人人玩 | 亚洲一级黄色片 | 欧美巨乳波霸 | 狠狠色伊人亚洲综合网站色 | 成人免费在线播放视频 | 亚洲国产日韩欧美 | 日韩欧美在线视频一区二区三区 | 国产在线视频一区二区三区 | 久久中文字幕在线视频 | 欧美日韩中文字幕综合视频 | 久久久久久久久久久福利 | 精品产品国产在线不卡 | se婷婷 | 日韩av二区| 肉色欧美久久久久久久免费看 | 久久久免费在线观看 | 国产黄色av网站 | 五月丁色| 三级视频国产 | 久草网站在线 | 天堂中文在线播放 | 免费观看国产成人 | 国产二区精品 | 综合色站 | 女人高潮特级毛片 | 国产最新福利 | 麻豆传媒电影在线观看 | 国产一区二区久久精品 | 天天插天天干天天操 | 天天草天天操 | 在线观看中文字幕亚洲 | 日韩免费视频线观看 | 黄色成人av | 日韩系列在线观看 | 一级一片免费看 | 亚洲精品影视在线观看 | 亚洲国产中文字幕 | 久久精品观看 | 天天干天天想 | 欧美在线观看视频 | 91探花视频 | 91在线播| 在线观看一区二区精品 | 五月婷婷狠狠 | 国产精品一区二区三区在线看 | 午夜视频在线观看一区二区三区 | 黄av资源| 成年在线观看 | 美女网站在线观看 | 久久97久久97精品免视看 | 日韩av午夜 | 日韩高清免费电影 | 欧美男同网站 | 国产自产高清不卡 | 黄色成品视频 | 久久午夜电影院 | 中文字幕在线免费97 | 成年人毛片在线观看 | 免费观看91 | 亚洲天堂精品视频在线观看 | 福利视频一区二区 | 五月婷婷中文 | 黄色亚洲大片免费在线观看 | 日本中文字幕视频 | 亚洲精品在线免费 | 91精选在线| 天堂av中文字幕 | 国产精品国产三级国产aⅴ无密码 | 国产成人久久精品一区二区三区 | 国产一级免费片 | 波多野结衣在线观看一区 | 亚洲国产精品推荐 | 午夜色性片 | 美女在线黄 | 成人影视免费看 | 国产一区成人 | 伊人伊成久久人综合网站 | 日批视频 | 午夜电影av | 国产婷婷在线观看 | 精品少妇一区二区三区在线 | 久久免费国产 | 国产97在线视频 | 亚洲一区二区三区毛片 | 国产精品a久久久久 | 国产成人综合精品 | 黄色片免费看 | 精品二区久久 | 97在线观 | 婷婷新五月 | 麻豆你懂的| 在线91观看 | 精品国产资源 | 亚洲va天堂va欧美ⅴa在线 | 蜜桃视频日本 | 中文字幕久久久精品 | 91污在线| 波多野结衣电影一区二区三区 | 一区二区伦理电影 | 国产精品一区二区 91 | 国产免费a | 国产黄a三级 | 最新国产一区二区三区 | 伊人www22综合色 | 色伊人网 | 国产在线精品观看 | 欧美日韩一级久久久久久免费看 | 亚洲永久精品一区 | 免费视频xnxx com | 国产黄色高清 | 欧美日韩大片在线观看 | 亚洲三级黄 | 成人在线观看av | 久草精品在线 | 欧美一级激情 | 久久er99热精品一区二区三区 | 天天视频色 | 一二三精品视频 | 岛国精品一区二区 | 日韩电影中文字幕在线观看 | 亚洲激情在线视频 | 精品一区二区综合 | 久99久久| 在线视频亚洲 | 九七视频在线观看 | 国产精品成人免费 | 亚洲精品色婷婷 | 色婷婷综合成人av | 我要看黄色一级片 | 日韩欧美精品一区二区三区经典 | 狠狠躁日日躁狂躁夜夜躁av | 国产黄a三级三级 | 久久精品免费电影 | 色综合久久综合网 | 婷婷中文在线 | 日韩电影在线观看一区 | 看片黄网站| 黄污视频网站大全 | 免费观看视频的网站 | 久久久私人影院 | 亚洲综合狠狠干 | 国产二区精品 | 久久草网| 亚洲精品成人av在线 | 探花视频网站 | 欧美日韩在线免费观看 | 国产精品短视频 | 国产精品毛片久久久久久久 | adn—256中文在线观看 | 99在线高清视频在线播放 | 色小说在线 | 黄色大片免费播放 | 国产精品久久久久久五月尺 | 性色在线视频 | 国产成人精品日本亚洲999 | 亚洲mv大片欧洲mv大片免费 | 久久精品99久久 | 黄色三几片 | 中文字幕在线一区二区三区 | 欧美日韩国产mv | 99色国产| 久久久免费看片 | 日韩欧美一区二区三区免费观看 | 久色免费视频 | 4438全国亚洲精品观看视频 | 日本精品中文字幕在线观看 | 天天色天天射天天干 | 欧美精品亚洲精品日韩精品 | 午夜久操 | 手机看片国产 | 亚洲精品久久久蜜桃直播 | 日本性动态图 | 精品一区二区免费在线观看 | 人人爱在线视频 | 日韩黄色av网站 | 91原创在线观看 | 香蕉视频在线免费看 | 欧美日本在线观看视频 | 黄色软件在线观看视频 | 久久久国际精品 | 日韩在线观看视频在线 | 亚洲网站在线 | 精品在线亚洲视频 | 精品国产乱码久久久久久久 | 波多野结衣在线观看视频 | 国产精品久久久久久久久久久久 | 永久免费毛片在线观看 | 五月天久久狠狠 | 日韩免费一区二区三区 | 日日天天 | 狠狠干中文字幕 | 西西44人体做爰大胆视频 | av中文字幕不卡 | 日韩中文字幕免费视频 | 国产精品国产三级国产aⅴ无密码 | 国产精品久久久电影 | a√资源在线 | 亚洲激情视频在线观看 | 国内精品视频免费 | 一级免费看 | 成人午夜av电影 | 成人一级免费电影 | 欧美久久久久久久久久久久久 | av免费观看高清 | 成人午夜久久 | 在线黄频| 国产中文字幕av | 最近中文字幕大全 | 久久精品资源 | 777xxx欧美| 久久夜色网 | 亚洲国产一区在线观看 | 一区二区三区手机在线观看 | 夜夜婷婷 | 在线综合色 | av一区在线播放 | 欧美肥妇free | 在线视频第一页 | 国产精品久久久久久久久久久久久久 | 少妇视频在线播放 | 亚洲成av人片在线观看香蕉 | 手机看片国产 | 欧洲精品亚洲精品 | 国产无套视频 | 久久社区视频 | 黄污视频网站大全 | 天堂av在线网站 | 伊人午夜| 青青射| 国产精品片 | 热久在线| 午夜影院先 | 96亚洲精品久久久蜜桃 | 懂色av懂色av粉嫩av分享吧 | 开心激情婷婷 | 亚洲精品啊啊啊 | 美女搞黄国产视频网站 | 91免费高清视频 | 超碰人人草 | 国产在线精品一区二区三区 | 日本aaaa级毛片在线看 | 日韩夜夜爽 | 超碰电影在线观看 | 日本久久高清视频 | 日韩高清观看 | 三级黄色在线 | 免费在线观看黄 | 精品国产欧美一区二区 | 成人免费网站在线观看 | 91社区国产高清 | 天天草天天 | 国产精品视频全国免费观看 | 国产免费成人av | 成人欧美一区二区三区在线观看 | 色操插| mm1313亚洲精品国产 | 99精品在线看 | 亚洲午夜av久久乱码 | 99九九视频 | 久久视 | 99国产在线观看 | av大片免费看 | 最新日韩在线观看视频 | 亚洲电影久久 | 日韩中字在线 | 亚洲午夜精品在线观看 | 又污又黄网站 | 国产大片免费久久 | av成人资源 | 亚洲少妇xxxx | 日本不卡123| 欧美日韩免费在线视频 | 欧美另类交在线观看 | 精品久久久久久久久久久院品网 | 天天草天天操 | 色偷偷888欧美精品久久久 | 亚洲免费色 | 久草在线综合网 | 精品国产一区二区三区久久久久久 | 97在线播放| 久草视频精品 | 国产精品久久久久永久免费看 | 在线精品观看国产 | 国产精品久久二区 | 五月天高清欧美mv | 一级欧美日韩 | 九色精品免费永久在线 | 成年人免费av网站 | 久久久久久久18 | 97精品国自产拍在线观看 | 亚洲资源 | 欧美久久九九 | 久草在线播放视频 | 玖玖精品视频 | 成人97视频一区二区 | 日韩久久一区二区 | 亚洲欧美国产精品va在线观看 | 免费看污黄网站 | 91大神免费在线观看 | 伊人资源视频在线 | 成人国产一区二区 | 天天射天天 | 永久免费观看视频 | 伊人久在线 | 国产黄网在线 | 天天色天天草天天射 | 精品视频久久 | a黄色 | 亚洲在线观看av | 国产美女在线精品免费观看 | 免费在线观看黄色网 | 日韩精品一区二区电影 | 99视频免费播放 | 久久久亚洲国产精品麻豆综合天堂 | 日韩黄色av网站 | 日本精品小视频 | 在线免费黄色片 | 久久久国产一区二区 | 欧美在线视频一区二区三区 | 日本超碰在线 | 亚洲欧美日韩在线看 | 在线不卡视频 | 国内精品久久久久影院一蜜桃 | 久草在线免费色站 | 黄色三级网站 | 亚洲成人av免费 | www亚洲精品 | 奇米先锋 | 一区二区三区在线免费播放 | 麻豆一级视频 | 国产精品一区二区三区免费视频 | 98久久 | av三区在线 | 日韩欧美中文 | 国产中文欧美日韩在线 | 国产中文字幕在线看 | 999抗病毒口服液 | 欧美日韩在线播放 | 在线观看蜜桃视频 | 97在线免费视频观看 | 韩国一区二区三区在线观看 | 亚洲极色 | 久青草视频|