Hi,大家好,我是明哥。
在自己學習 Golang 的這段時間里,我寫了詳細的學習筆記放在我的個人微信公眾號 《Go編程時光》,對於 Go 語言,我也算是個初學者,因此寫的東西應該會比較適合剛接觸的同學,如果你也是剛學習 Go 語言,不防關注一下,一起學習,一起成長。
我的在線博客:http://golang.iswbm.com
我的 Github:github.com/iswbm/GolangCodingTime
1. 靜態類型
所謂的靜態類型(即 static type),就是變量聲明的時候的類型。
var age int // int 是靜態類型
var name string // string 也是靜態類型
它是你在編碼時,肉眼可見的類型。
2. 動態類型
所謂的 動態類型(即 concrete type,也叫具體類型)是 程序運行時系統才能看見的類型。
這是什么意思呢?
我們都知道 空接口 可以承接什么問題類型的值,什么 int 呀,string 呀,都可以接收。
比如下面這幾行代碼
var i interface{}
i = 18
i = "Go編程時光"
第一行:我們在給 i
聲明了 interface{}
類型,所以 i
的靜態類型就是 interface{}
第二行:當我們給變量 i
賦一個 int 類型的值時,它的靜態類型還是 interface{},這是不會變的,但是它的動態類型此時變成了 int 類型。
第三行:當我們給變量 i
賦一個 string 類型的值時,它的靜態類型還是 interface{},它還是不會變,但是它的動態類型此時又變成了 string 類型。
從以上,可以知道,不管是 i=18
,還是 i="Go編程時光"
,都是當程序運行到這里時,變量的類型,才發生了改變,這就是我們最開始所說的 動態類型是程序運行時系統才能看見的類型。
3. 接口組成
每個接口變量,實際上都是由一 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
4. 接口細分
根據接口是否包含方法,可以將接口分為 iface
和 eface
。
iface
第一種: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
第二種:eface,表示不帶有方法的接口
比如
var i interface{}
eface 的源碼如下:
// src/runtime/runtime2.go
// 空接口
type eface struct {
_type *_type
data unsafe.Pointer
}
5.理解動態類型
前兩節,我們知道了什么是動態類型?如何讓一個對象具有動態類型?
后兩節,我們知道了接口分兩種,它們的內部結構各是什么樣的?
那最后一節,可以將前面四節的內容結合起來,看看在給一個空接口類型的變量賦值時,接口的內部結構會發生怎樣的變化 。
iface
先來看看 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
再來看看 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
。
6. 反射的必要性
由於動態類型的存在,在一個函數中接收的參數的類型有可能無法預先知曉,此時我們就要對參數進行反射,然后根據不同的類型做不同的處理。
關於 反射 的內容有點多,我將其安排在下一篇。