您好,登錄后才能下訂單哦!
這篇文章主要介紹“Golang中Interface接口的三個(gè)特性是什么”的相關(guān)知識,小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“Golang中Interface接口的三個(gè)特性是什么”文章能幫助大家解決問題。
因?yàn)橛成浣ㄔO(shè)在類型的基礎(chǔ)之上,首先我們對類型進(jìn)行全新的介紹。
go是一個(gè)靜態(tài)性語言,每個(gè)變量都有靜態(tài)的類型,因此每個(gè)變量在編譯階段中有明確的變量類型,比如像:int、float32、MyType。。。
比如:
type MyInt int var i int var j MyInt
變量i的類型為int,變量j的類型為MyInt,變量i、j具有確定的類型,雖然i、j的潛在類型是一樣的,但是在沒有轉(zhuǎn)換的情況下他們之間不能相互賦值。
在類型中有重要的一類為接口類型(interface),接口類型為一系列方法的集合。一個(gè)接口型變量可以存儲(chǔ)接口方法中聲明的任何具體的值。像io.Reader和io.Writer是一個(gè)很好的例子,這兩個(gè)接口在io包中定義。
type Reader interface{ Read(p []byte)(n int, err error) } type Writer interface{ Writer(p []byte)(n int,er error) }
任何聲明為io.Reader或者io.Writer類型的變量都可以使用Read或者Writer 方法。也就意味著io.Reader類型的變量可以賦值任何有Read方法的的變量。
var r io.Reader r = os.Stdin r = bufio.NewReader(r) r = new(bytes.Buffer)
無論變量r被賦值什么類型的值,變量r的類型依舊是io.Reader。go語言是靜態(tài)類型語言,并且r的類型永遠(yuǎn)是io.Reader。
在接口類型中有一個(gè)重要的極端接口類型--空接口。
interface{}
他代表一個(gè)空的方法集合并且可以被賦值為任何值,因?yàn)槿魏我粋€(gè)變量都有0個(gè)或者多個(gè)方法。
有一種錯(cuò)誤的說法是go的接口類型是動(dòng)態(tài)定義的,其實(shí)在go中他們是靜態(tài)定義的,一個(gè)接口類型的變量總是有著相同類型的類型,盡管在運(yùn)行過程中存儲(chǔ)在接口類型變量的值具有不同的類型,但是接口類型的變量永遠(yuǎn)是靜態(tài)的類型。
關(guān)于go中接口類型的表示方法Russ Cox大神在一篇博客中已經(jīng)詳細(xì)介紹[blog:http://research.swtch.com/2009/12/go-data-structures-interfaces.html]
一個(gè)接口類型的變量存儲(chǔ)一對信息:具體值,值的類型描述。更具體一點(diǎn)是,值是實(shí)現(xiàn)接口的底層具體數(shù)據(jù)項(xiàng),類型是數(shù)據(jù)項(xiàng)類型的完整描述。
舉個(gè)例子:
var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty
變量r包含兩個(gè)數(shù)據(jù)項(xiàng):值(tty),類型(os.File)。注意os.File實(shí)現(xiàn)的方法不僅僅是Read,即使接口類型僅包含Read方法,但是值(tty)卻用于其完整的類型信息,因此我們可以按照如下方法調(diào)用
var w io.Writer w = r.(io.Writer)
這條語句是一個(gè)斷言語句,斷言的意思是變量r中的數(shù)據(jù)項(xiàng)聲明為io.Writer,因?yàn)槲覀兛梢詫賦值給w。執(zhí)行完這條語句以后,變量w將和r一樣包含值(tty)、類型(*os.File)。即使具體值可能包含很多方法,但是接口的靜態(tài)類型決定什么方法可以通過接口型變量調(diào)用。
同樣我們可以
var empty interface{} empty = w
這個(gè)接口型變量同樣包含一個(gè)數(shù)據(jù)對(tty,*os.File)??战涌诳梢越邮苋魏晤愋偷淖兞浚⑶野覀兛赡苡玫降年P(guān)于這個(gè)變量的所有信息。在這里我們不需要斷言是因?yàn)閣變量滿足于空接口。在上一個(gè)從Reader向Writer移動(dòng)數(shù)據(jù)的例子中,我們需要類型斷言,因?yàn)镽eader接口中不包含Writer方法
切記接口的數(shù)據(jù)對中的內(nèi)容只能來自于(value , concrete type)而不能是(value, interface type),也就是接口類型不能接受接口類型的變量。
在最底層,映射是對存儲(chǔ)在接口內(nèi)部數(shù)據(jù)對(值、類型)的解釋機(jī)制。首先我們需要知道在reflect包中的兩種類型Type和Value,這兩種類型提供了對接口變量內(nèi)部內(nèi)容的訪問,同時(shí)reflect.TypeOf和reflect.ValueOf兩個(gè)方法檢索接口類型的變量。
首先我們開始TypeOf
package main import ( "fmt" "reflect" ) func main() { var f float64 = 13.4 fmt.Println(reflect.TypeOf(f)) fmt.Println("Hello, playground") }
結(jié)果
float64
Hello, playground
我們可以會(huì)感到奇怪這里沒有接口呀?因?yàn)樵诔绦蛑形覀兛梢缘弥猣的變量類型應(yīng)為float32,不應(yīng)該是什么變量類型。但是我們在golang源碼中我們得知,reflect.TypeOf包含一個(gè)空接口類型的變量.
func TypeOf(i interface{})Type
當(dāng)我們在調(diào)用reflect.TypeOf方法時(shí),x首先存儲(chǔ)在一個(gè)空的接口中,然后再作為一個(gè)參數(shù)傳送到reflect.TypeOf方法中,然后該方法解壓這個(gè)空的接口得到類型信息。
同樣reflect.ValueOf方法,得到值。
var f float64 = 13.4 fmt.Println(reflect.ValueOf(f))
結(jié)果
13.4
reflect.Type和reflec.Value有許多方法讓我們檢查和修改它們。一個(gè)比較重要的方法是Value有一個(gè)能夠返回reflect.Value的類型的方法Type。另外一個(gè)比較重要的是Type和Value都提供一個(gè)Kind方法,該方法能夠返回存儲(chǔ)數(shù)據(jù)項(xiàng)的字長(Uini,Floatr64,Slice等等)。同樣Value方法也提供一些叫做Int、Float的方法讓我們修改存儲(chǔ)在內(nèi)部的值。
var f float64 = 13.44444 v := reflect.ValueOf(f) fmt.Println(v) fmt.Println(v.Type()) fmt.Println(v.Kind()) fmt.Println(v.Float())
結(jié)果
13.444444444444445
float64
float64
13.444444444444445
同時(shí)有像SetInt、SetFloat之類的方法,但是我們必須謹(jǐn)慎的使用它們。
反射機(jī)制有兩個(gè)重要的性質(zhì)。首先,為了保證接口的簡潔行,getter
和setter
兩個(gè)方法是可以接受最大類型值的賦值,比如int64
可以接受任何符號整數(shù)。所以值的Int方法會(huì)返回一個(gè)int64
類型的值,SetInt
接受int64
類型的值,因此它可能轉(zhuǎn)化為所涉及的實(shí)際類型。
var x uint8 = 'x' v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) // uint8. fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true. x = uint8(v.Uint()) // v.Uint returns a uint64.
第二個(gè)特性:接口保存了數(shù)據(jù)項(xiàng)底層類型,而不是靜態(tài)的類型,如果一個(gè)接口包含用戶定義的整數(shù)類型的值,比如
type MyInt int var x MyInt = 7 v := reflect.ValueOf(x)
則v的Kind
方法調(diào)用仍然返回的是reflect.Int
,盡管x的靜態(tài)類型是MyInt。也可以說,Kind`不會(huì)像
Type`一樣將MyInt和int當(dāng)作兩種類型來對待。
像物理映射一樣,Go中的映射也有其自身的相反性。
通過利用Interface
的方法我們可以將interface.Value
恢復(fù)至接口類型,實(shí)際上這個(gè)方法將type和value信息包裝至interface類型并且返回該值。
// Interface returns v's value as an interface{}. func (v Value) Interface() interface{}
因此我們可以說
y := v.Interface().(float64) // y will have type float64. fmt.Println(y)
打印float64類型的值,其實(shí)是接口類型變量v的映射。
或者我們可以這樣做,fmt.Println
, fmt.Printf
等函數(shù)的參數(shù)盡管是空的接口類型也能運(yùn)行,在fmt包里面解析出type和value的方法和我們上面的例子相似。因此所有正確打印reflect.Value
的方法都試通過interface的方法將值傳遞給格式化打印函數(shù)。
fmt.Println(v.Interface())
(為什么不是fmt.Println(v)
?因?yàn)橥ㄟ^v是reflect.Value類型.)因?yàn)槲覀兊闹档讓邮莊loat64類型,因此我們甚至可以浮點(diǎn)類型的格式打印.
fmt.Printf("value is %7.1e\n", v.Interface())
結(jié)果是
3.4e+00
因此我們不用類型斷言v.Interface{}到float64類型。因?yàn)榻涌陬愋蛢?nèi)部保存著值的信息,Printf函數(shù)能夠恢復(fù)這些信息。
簡單的說Interface是ValueOf的反操作,除非這個(gè)值總是靜態(tài)的Interface類型。
第三法則比較微妙并且容易混淆,但是如果從第一準(zhǔn)則開始看的話,那么還是比較容易理解的。
這是一條錯(cuò)誤的語句,但是這個(gè)錯(cuò)誤值得我們研究
var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1) // Error: will panic.
如果你運(yùn)行這條語句則會(huì)有下面的報(bào)錯(cuò)信息
panic: reflect.Value.SetFloat using unaddressable value
因?yàn)樽兞縱是不可更改的,所以提示值7.1是不可尋址的。可賦值是value的一個(gè)特性,但是并不是所以的value都具有這個(gè)特性。
CanSet
方法返回該值是否是可以改變的,比如
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("settability of v:", v.CanSet())
結(jié)果是
settability of v: false
如果在不可以賦值的變量上進(jìn)行賦值,就回引起錯(cuò)誤。但是到底是什么才是可以賦值的呢?
可賦值的有點(diǎn)像是可尋址的,但是會(huì)更嚴(yán)格。映射對象可以更改存儲(chǔ)值的特性可以用來創(chuàng)建新的映射對象。映射對象包含原始的數(shù)據(jù)項(xiàng)是決定映射對象可賦值的關(guān)鍵。當(dāng)下面代碼運(yùn)行時(shí)
var x float64 = 3.4 v := reflect.ValueOf(x)
只是將x的拷貝到reflect.ValueOf
,因此reflect.ValueOf
的返回值是x的復(fù)制項(xiàng),而不是x本身。假如下面這條語句可以正常運(yùn)行
v.SetFloat(5.4)
盡管v看起來是由x創(chuàng)建的,但是并不會(huì)更新x的值,因?yàn)檫@條語句會(huì)更新x拷貝值的值,但是并不影響x本身,因此可更改的這一特性就是為了避免這種操作。
雖然這看起來很古怪,但其實(shí)這是一種很熟悉的操作。比如我們將x值賦值給一個(gè)方法
f(x)
我們本身不想修改x的值,因?yàn)閭魅氲闹皇莤值的拷貝,但是如果我們想修改x的值,那么我們需要傳送x的地址(也就是x的指針)
f(&x)
這種操作是簡單明了的,其實(shí)對于映射也是一樣的。如果我們想通過映射修改x的值,那么我們需要傳送x的指針。比如
var x float64 = 3.4 p := reflect.ValueOf(&x) // Note: take the address of x. fmt.Println("type of p:", p.Type()) fmt.Println("settability of p:", p.CanSet())
結(jié)果
type of p: *float64
settability of p: false
映射對象p仍然是不可修改的,但是其實(shí)我們并不想修改p,而是*p。為了得到指針的指向,我們需要使用Elem()
方法,該方法將會(huì)指向*p的值,并且將其保存到映射變量中
v := p.Elem() fmt.Println("settability of v:", v.CanSet())
結(jié)果為
settability of v: true
現(xiàn)在v是一個(gè)可修改的映射對象。并且v代表x,因此我們可以使用v.SetFloat()
來修改x的值。
v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x)
輸出結(jié)果為
7.1
7.1
映射是比較難理解的,盡管我們通過映射的Values``Types
隱藏了到底發(fā)生了什么操作。我們只需要記住如果想改變它的值,那在調(diào)用ValuesOf
方法時(shí)應(yīng)該使用指向它的指針。
在上一個(gè)例子中v并不是指向自身的指針,而是通過其他方式產(chǎn)生的。還有一種常用的操作就是修改結(jié)構(gòu)體的某個(gè)字段,只要我們知道了結(jié)構(gòu)體的地址,我們就能修改它的字段。
這有一個(gè)修改結(jié)構(gòu)體變量t的例子。因?yàn)槲覀円薷慕Y(jié)構(gòu)體的字段,所以我們使用結(jié)構(gòu)體指針創(chuàng)建結(jié)構(gòu)體對象。我們使用typeOfT代表t的數(shù)據(jù)類型,并通過NumField方法迭代結(jié)構(gòu)體的字段。主意:我們只是提取出結(jié)構(gòu)體類型字段的的名字,而他們的reflect.Value
對象。
type T struct { A int B string } t := T{23, "skidoo"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) }
輸出結(jié)果是
0: A int = 23
1: B string = skidoo
值得注意的是只有可導(dǎo)出的字段才能使可修改的。
因?yàn)閟包含一個(gè)可修改的映射對象,所以我們可以修改結(jié)構(gòu)體的字段
s.Field(0).SetInt(77) s.Field(1).SetString("Sunset Strip") fmt.Println("t is now", t)
結(jié)果為
t is now {77 Sunset Strip}
如果s是通過t創(chuàng)建而不是&t,那么SetInt和SetString方法都會(huì)出錯(cuò),因?yàn)閠的字段是不可以修改的。
關(guān)于“Golang中Interface接口的三個(gè)特性是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識點(diǎn)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。