Hi,你們好,我是明哥。git
在本身學習 Golang 的這段時間裏,我寫了詳細的學習筆記放在個人我的微信公衆號 《Go編程時光》,對於 Go 語言,我也算是個初學者,所以寫的東西應該會比較適合剛接觸的同窗,若是你也是剛學習 Go 語言,不防關注一下,一塊兒學習,一塊兒成長。github
個人在線博客:http://golang.iswbm.com
個人 Github:github.com/iswbm/GolangCodingTimegolang
所謂的靜態類型(即 static type),就是變量聲明的時候的類型。編程
var age int // int 是靜態類型 var name string // string 也是靜態類型
它是你在編碼時,肉眼可見的類型。微信
所謂的 動態類型(即 concrete type,也叫具體類型)是 程序運行時系統才能看見的類型。函數
這是什麼意思呢?學習
咱們都知道 空接口 能夠承接什麼問題類型的值,什麼 int 呀,string 呀,均可以接收。ui
好比下面這幾行代碼編碼
var i interface{} i = 18 i = "Go編程時光"
第一行:咱們在給 i
聲明瞭 interface{}
類型,因此 i
的靜態類型就是 interface{}
3d
第二行:當咱們給變量 i
賦一個 int 類型的值時,它的靜態類型仍是 interface{},這是不會變的,可是它的動態類型此時變成了 int 類型。
第三行:當咱們給變量 i
賦一個 string 類型的值時,它的靜態類型仍是 interface{},它仍是不會變,可是它的動態類型此時又變成了 string 類型。
從以上,能夠知道,無論是 i=18
,仍是 i="Go編程時光"
,都是當程序運行到這裏時,變量的類型,才發生了改變,這就是咱們最開始所說的 動態類型是程序運行時系統才能看見的類型。
每一個接口變量,實際上都是由一 pair 對(type 和 data)組合而成,pair 對中記錄着實際變量的值和類型。
好比下面這條語句
var age int = 25
咱們聲明瞭一個 int 類型變量,變量名叫 age ,其值爲 25
知道了接口的組成後,咱們在定義一個變量時,除了使用常規的方法(可參考:02. 學習五種變量建立的方法)
也可使用像下面這樣的方式
package main import "fmt" func main() { age := (int)(25) //或者使用 age := (interface{})(25) fmt.Printf("type: %T, data: %v ", age, age) }
輸出以下
type: int, data: 25
根據接口是否包含方法,能夠將接口分爲 iface
和 eface
。
第一種:iface,表示帶有一組方法的接口。
好比
type Phone interface { call() }
iface
的具體結構可用以下一張圖來表示
iface 的源碼以下:
// runtime/runtime2.go // 非空接口 type iface struct { tab *itab data unsafe.Pointer } // 非空接口的類型信息 type itab struct { inter *interfacetype // 接口定義的類型信息 _type *_type // 接口實際指向值的類型信息 link *itab bad int32 inhash int32 fun [1]uintptr // 接口方法實現列表,即函數地址列表,按字典序排序 } // runtime/type.go // 非空接口類型,接口定義,包路徑等。 type interfacetype struct { typ _type pkgpath name mhdr []imethod // 接口方法聲明列表,按字典序排序 } // 接口的方法聲明 type imethod struct { name nameOff // 方法名 ityp typeOff // 描述方法參數返回值等細節 }
第二種:eface,表示不帶有方法的接口
好比
var i interface{}
eface 的源碼以下:
// src/runtime/runtime2.go // 空接口 type eface struct { _type *_type data unsafe.Pointer }
前兩節,咱們知道了什麼是動態類型?如何讓一個對象具備動態類型?
後兩節,咱們知道了接口分兩種,它們的內部結構各是什麼樣的?
那最後一節,能夠將前面四節的內容結合起來,看看在給一個空接口類型的變量賦值時,接口的內部結構會發生怎樣的變化 。
先來看看 iface,有以下一段代碼:
var reader io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } reader = tty
第一行代碼:var reader io.Reader ,因爲 io.Reader 接口包含 Read 方法,因此 io.Reader 是 iface
,此時 reader 對象的靜態類型是 io.Reader,暫無動態類型。
最後一行代碼:reader = tty,tty 是一個 *os.File
類型的實例,此時reader 對象的靜態類型仍是 io.Reader,而動態類型變成了 *os.File
。
再來看看 eface,有以下一段代碼:
//不帶函數的interface var empty interface{} tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } empty = tty
第一行代碼:var empty interface{},因爲 interface{}
是一個 eface,其只有一個 _type
能夠存放變量類型,此時 empty 對象的(靜態)類型是 nil。
最後一行代碼:empty = tty,tty 是一個 *os.File
類型的實例,此時 _type
變成了 *os.File
。
因爲動態類型的存在,在一個函數中接收的參數的類型有可能沒法預先知曉,此時咱們就要對參數進行反射,而後根據不一樣的類型作不一樣的處理。
關於 反射 的內容有點多,我將其安排在下一篇。