<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
第一次翻譯文章,請各路人士多多指教!
因為對映建設在型別的基礎之上,首先我們對型別進行全新的介紹。
go是一個靜態性語言,每個變數都有靜態的型別,因此每個變數在編譯階段中有明確的變數型別,比如像:int、float32、MyType。。。
比如:
type MyInt int var i int var j MyInt
變數i的型別為int,變數j的型別為MyInt,變數i、j具有確定的型別,雖然i、j的潛在型別是一樣的,但是在沒有轉換的情況下他們之間不能相互賦值。
在型別中有重要的一類為介面型別(interface),介面型別為一系列方法的集合。一個介面型變數可以儲存介面方法中宣告的任何具體的值。像io.Reader和io.Writer是一個很好的例子,這兩個介面在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語言是靜態型別語言,並且r的型別永遠是io.Reader。
在介面型別中有一個重要的極端介面型別--空介面。
interface{}
他代表一個空的方法集合並且可以被賦值為任何值,因為任何一個變數都有0個或者多個方法。
有一種錯誤的說法是go的介面型別是動態定義的,其實在go中他們是靜態定義的,一個介面型別的變數總是有著相同型別的型別,儘管在執行過程中儲存在介面型別變數的值具有不同的型別,但是介面型別的變數永遠是靜態的型別。
關於go中介面型別的表示方法Russ Cox大神在一篇部落格中已經詳細介紹[blog:http://research.swtch.com/2009/12/go-data-structures-interfaces.html]
一個介面型別的變數儲存一對資訊:具體值,值的型別描述。更具體一點是,值是實現介面的底層具體資料項,型別是資料項型別的完整描述。
舉個例子:
var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty
變數r包含兩個資料項:值(tty),型別(os.File)。注意os.File實現的方法不僅僅是Read,即使介面型別僅包含Read方法,但是值(tty)卻用於其完整的型別資訊,因此我們可以按照如下方法呼叫
var w io.Writer w = r.(io.Writer)
這條語句是一個斷言語句,斷言的意思是變數r中的資料項宣告為io.Writer,因為我們可以將r賦值給w。執行完這條語句以後,變數w將和r一樣包含值(tty)、型別(*os.File)。即使具體值可能包含很多方法,但是介面的靜態型別決定什麼方法可以通過介面型變數呼叫。
同樣我們可以
var empty interface{} empty = w
這個介面型變數同樣包含一個資料對(tty,*os.File)。空介面可以接受任何型別的變數,並且包含我們可能用到的關於這個變數的所有資訊。在這裡我們不需要斷言是因為w變數滿足於空介面。在上一個從Reader向Writer行動資料的例子中,我們需要型別斷言,因為Reader介面中不包含Writer方法
切記介面的資料對中的內容只能來自於(value , concrete type)而不能是(value, interface type),也就是介面型別不能接受介面型別的變數。
在最底層,對映是對儲存在介面內部資料對(值、型別)的解釋機制。首先我們需要知道在reflect包中的兩種型別Type和Value,這兩種型別提供了對介面變數內部內容的存取,同時reflect.TypeOf和reflect.ValueOf兩個方法檢索介面型別的變數。
首先我們開始TypeOf
package main import ( "fmt" "reflect" ) func main() { var f float64 = 13.4 fmt.Println(reflect.TypeOf(f)) fmt.Println("Hello, playground") }
結果
float64
Hello, playground
我們可以會感到奇怪這裡沒有介面呀?因為在程式中我們可以得知f的變數型別應為float32,不應該是什麼變數型別。但是我們在golang原始碼中我們得知,reflect.TypeOf包含一個空介面型別的變數.
func TypeOf(i interface{})Type
當我們在呼叫reflect.TypeOf方法時,x首先儲存在一個空的介面中,然後再作為一個引數傳送到reflect.TypeOf方法中,然後該方法解壓這個空的介面得到型別資訊。
同樣reflect.ValueOf方法,得到值。
var f float64 = 13.4 fmt.Println(reflect.ValueOf(f))
結果
13.4
reflect.Type和reflec.Value有許多方法讓我們檢查和修改它們。一個比較重要的方法是Value有一個能夠返回reflect.Value的型別的方法Type。另外一個比較重要的是Type和Value都提供一個Kind方法,該方法能夠返回儲存資料項的字長(Uini,Floatr64,Slice等等)。同樣Value方法也提供一些叫做Int、Float的方法讓我們修改儲存在內部的值。
var f float64 = 13.44444 v := reflect.ValueOf(f) fmt.Println(v) fmt.Println(v.Type()) fmt.Println(v.Kind()) fmt.Println(v.Float())
結果
13.444444444444445
float64
float64
13.444444444444445
同時有像SetInt、SetFloat之類的方法,但是我們必須謹慎的使用它們。
反射機制有兩個重要的性質。首先,為了保證介面的簡潔行,getter
和setter
兩個方法是可以接受最大型別值的賦值,比如int64
可以接受任何符號整數。所以值的Int方法會返回一個int64
型別的值,SetInt
接受int64
型別的值,因此它可能轉化為所涉及的實際型別。
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.
第二個特性:介面儲存了資料項底層型別,而不是靜態的型別,如果一個介面包含使用者定義的整數型別的值,比如
type MyInt int var x MyInt = 7 v := reflect.ValueOf(x)
則v的Kind
方法呼叫仍然返回的是reflect.Int
,儘管x的靜態型別是MyInt。也可以說,Kind`不會像
Type`一樣將MyInt和int當作兩種型別來對待。
像物理對映一樣,Go中的對映也有其自身的相反性。
通過利用Interface
的方法我們可以將interface.Value
恢復至介面型別,實際上這個方法將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型別的值,其實是介面型別變數v的對映。
或者我們可以這樣做,fmt.Println
, fmt.Printf
等函數的引數儘管是空的介面型別也能執行,在fmt包裡面解析出type和value的方法和我們上面的例子相似。因此所有正確列印reflect.Value
的方法都試通過interface的方法將值傳遞給格式化列印函數。
fmt.Println(v.Interface())
(為什麼不是fmt.Println(v)
?因為通過v是reflect.Value型別.)因為我們的值底層是float64型別,因此我們甚至可以浮點型別的格式列印.
fmt.Printf("value is %7.1en", v.Interface())
結果是
3.4e+00
因此我們不用型別斷言v.Interface{}到float64型別。因為介面型別內部儲存著值的資訊,Printf函數能夠恢復這些資訊。
簡單的說Interface是ValueOf的反操作,除非這個值總是靜態的Interface型別。
第三法則比較微妙並且容易混淆,但是如果從第一準則開始看的話,那麼還是比較容易理解的。
這是一條錯誤的語句,但是這個錯誤值得我們研究
var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1) // Error: will panic.
如果你執行這條語句則會有下面的報錯資訊
panic: reflect.Value.SetFloat using unaddressable value
因為變數v是不可更改的,所以提示值7.1是不可定址的。可賦值是value的一個特性,但是並不是所以的value都具有這個特性。
CanSet
方法返回該值是否是可以改變的,比如
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("settability of v:", v.CanSet())
結果是
settability of v: false
如果在不可以賦值的變數上進行賦值,就回引起錯誤。但是到底是什麼才是可以賦值的呢?
可賦值的有點像是可定址的,但是會更嚴格。對映物件可以更改儲存值的特性可以用來建立新的對映物件。對映物件包含原始的資料項是決定對映物件可賦值的關鍵。當下面程式碼執行時
var x float64 = 3.4 v := reflect.ValueOf(x)
只是將x的拷貝到reflect.ValueOf
,因此reflect.ValueOf
的返回值是x的複製項,而不是x本身。假如下面這條語句可以正常執行
v.SetFloat(5.4)
儘管v看起來是由x建立的,但是並不會更新x的值,因為這條語句會更新x拷貝值的值,但是並不影響x本身,因此可更改的這一特性就是為了避免這種操作。
雖然這看起來很古怪,但其實這是一種很熟悉的操作。比如我們將x值賦值給一個方法
f(x)
我們本身不想修改x的值,因為傳入的只是x值的拷貝,但是如果我們想修改x的值,那麼我們需要傳送x的地址(也就是x的指標)
f(&x)
這種操作是簡單明瞭的,其實對於對映也是一樣的。如果我們想通過對映修改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())
結果
type of p: *float64
settability of p: false
對映物件p仍然是不可修改的,但是其實我們並不想修改p,而是*p。為了得到指標的指向,我們需要使用Elem()
方法,該方法將會指向*p的值,並且將其儲存到對映變數中
v := p.Elem() fmt.Println("settability of v:", v.CanSet())
結果為
settability of v: true
現在v是一個可修改的對映物件。並且v代表x,因此我們可以使用v.SetFloat()
來修改x的值。
v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x)
輸出結果為
7.1
7.1
對映是比較難理解的,儘管我們通過對映的Values``Types
隱藏了到底發生了什麼操作。我們只需要記住如果想改變它的值,那在呼叫ValuesOf
方法時應該使用指向它的指標。
在上一個例子中v並不是指向自身的指標,而是通過其他方式產生的。還有一種常用的操作就是修改結構體的某個欄位,只要我們知道了結構體的地址,我們就能修改它的欄位。
這有一個修改結構體變數t的例子。因為我們要修改結構體的欄位,所以我們使用結構體指標建立結構體物件。我們使用typeOfT代表t的資料型別,並通過NumField方法迭代結構體的欄位。主意:我們只是提取出結構體型別欄位的的名字,而他們的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 = %vn", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) }
輸出結果是
0: A int = 23
1: B string = skidoo
值得注意的是只有可匯出的欄位才能使可修改的。
因為s包含一個可修改的對映物件,所以我們可以修改結構體的欄位
s.Field(0).SetInt(77) s.Field(1).SetString("Sunset Strip") fmt.Println("t is now", t)
結果為
t is now {77 Sunset Strip}
如果s是通過t建立而不是&t,那麼SetInt和SetString方法都會出錯,因為t的欄位是不可以修改的。
原部落格地址:The Go Blog|The Laws of Reflection
到此這篇關於Go語言之interface介面的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45