34. 圖解 Go 語言:靜態類型與動態類型

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. 接口細分

根據接口是否包含方法,可以將接口分爲 ifaceeface

iface

第一種:iface,表示帶有一組方法的接口。

比如

type Phone interface {
   call()
}

iface 的具體結構可用如下一張圖來表示

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
}

eface 結構組成

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. 反射的必要性

由於動態類型的存在,在一個函數中接收的參數的類型有可能無法預先知曉,此時我們就要對參數進行反射,然後根據不同的類型做不同的處理。

關於 反射 的內容有點多,我將其安排在下一篇。

參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章