gopl 反射1

Go 語言提供了一種機制,在編譯時不知道類型的狀況下,可更新變量、在運行時查看值、調用方法以及直接對它們的佈局進行操做,這種機制稱爲反射(reflection)。 json

本篇各章節的主要內容: 數組

  1. 爲何使用反射:不使用反射會有哪些限制
  2. reflect 包的兩個主要類型:經過反射獲取基礎類型的值
  3. 遞歸地獲取組合類型的值
  4. 示例,編碼 S 表達式:以上內容的綜合運用

關於反射的文章,下面這篇也不錯的,條條理比較清晰,能夠參考。
Go語言基礎之反射:https://www.liwenzhou.com/posts/Go/13_reflect/ 安全

爲何使用反射

有時候咱們須要編寫一個函數,一個有能力統一處理各類值類型的函數。而這些類型可能沒法共享同一個接口,也可能佈局未知,還有可能這個類型在設計函數的時候還不存在。甚至這個類型會同時存在以上多個或所有的問題。 數據結構

格式化函數

一個熟悉的例子是 fmt.Printf 中的格式化邏輯,它能夠輸出任意類型的任意值,包括用戶自定義的類型。下面嘗試寫一個與 fmt.Sprint 相似的函數,只接收一個值而後返回字符串,函數名就稱爲 Sprint。
先用一個類型分支來判斷這個參數是否認義了 String 方法,若是有就調用它。而後添加一些 switch 分支來判斷參數的動態類型是不是基本類型,再對每種類型採用不一樣的格式化操做:ide

func Sprint(x interface{}) string {
    type stringer interface {
        String() string
    }
    switch x := x.(type) {
    case stringer:
        return x.String()
    case string:
        return x
    case int:
        return strconv.Itoa(x)
    // ...similar cases for int16, uint32, and so on...
    case bool:
        if x {
            return "true"
        }
        return "false"
    default:
        // array, chan, func, map, pointer, slice, struct
        return "???"
    }
}

到此,尚未用到反射。
對於複合數據類型,也能夠添加更多的分支。可是好比數組,不用的長度就是不同的類型,因此這樣的類型有無限多。另外還有自定義命名的類型。當咱們沒法透視一個未知類型的佈局時,這段代碼就沒法繼續,如今就須要反射了。 函數

reflect.Type 和 reflect.Value

反射功能由 reflect 包提供,它定義了兩個重要的類型:工具

  • Type :
  • Value :

Type

reflect.Type 是一個接口,每一個 Type 表示 Go 語言的一個類型。
reflect.TypeOf 函數接受 interface{} 參數,以 reflect.Type 的形式返回動態類型:佈局

t := reflect.TypeOf(3)  // a reflect.Type
fmt.Println(t.String()) // "int"
fmt.Println(t)          // "int"

由於 reflect.TypeOf 返回一個接口值對應的動態類型,因此它返回的老是具體類型而不是接口類型:post

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"

由於輸出一個接口值的動態類型在調試和日誌中很經常使用,因此 fmt.Printf 提供了一個簡單的方式 %T,內部的實現就是 reflect.TypeOf:ui

fmt.Printf("%T\n", 3) // "int"

Value

reflect.Value 是一個結構體類型,能夠包含一個任意類型的值。
reflect.ValueOf 函數接受 interface{} 參數,將接口的動態值以 reflect.Value 的形式返回。與 reflect.TypeOf 相似,reflect.Value 返回的結果也是具體類型,不過也能夠是一個接口值:

v := reflect.ValueOf(3) // a reflect.Value
fmt.Println(v)          // "3"
fmt.Printf("%v\n", v)   // "3"
fmt.Println(v.String()) // NOTE: "<int Value>"

reflect.Value 也知足 fmt.Stringer,但除非 Value 包含的是一個字符串,不然 String 方法的結果僅僅暴露類型。一般,須要 fmt 包的 %v 功能,它會對 reflect.Value 進行特殊處理。

Value 結構體的方法
調用 Value 的 Type 方法會把它的類型以 reflect.Type 方式返回:

t := v.Type()           // a reflect.Type
fmt.Println(t.String()) // "int"

reflect.ValueOf 的逆操做是 reflect.Value.Interface 方法。它返回一個 interface{},即空接口值,與 reflect.Value 包含同一個具體值:

v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface()      // an interface{}
i := x.(int)            // an int
fmt.Printf("%d\n", i)   // "3"

reflect.Value 和 interface{} 均可以包含任意的值。兩者的區別是空接口隱藏了值的佈局信息、內置操做和相關方法,因此除非知道它的動態類型,並用一個類型斷言來滲透進去(就如上面的代碼那樣),不然對所包含的值能作的事情不多。做爲對比,Value 有不少方法能夠用來分析所包含的值,而不用知道它的類型。

格式化函數

使用反射的技術,第二次嘗試寫一個通用的格式化函數,此次名稱叫: fotmat.Any。
不用類型分支,這裏用 reflec.Value 的 Kind 方法來區分不一樣的類型。儘管有無限種類型,但類型的分類(kind)只有少數幾種:

  • 基礎類型:
    • bool
    • String
    • 各類數字類型
  • 聚合類型:
    • Array
    • Struct
  • 引用類型
    • chan
    • Func
    • Ptr
    • Slice
    • Map
  • 接口類型:
    • interface

最後還有一個 Invalid 類型,表示它們尚未任何的值。(reflect.Value 的零值就屬於 Invalid 類型。)

package format

import (
    "reflect"
    "strconv"
)

// Any 把任何值格式化爲一個字符串
func Any(value interface{}) string {
    return formatAtom(reflect.ValueOf(value))
}

// formatAtom 格式化一個值,且不分析它的內部結構
func formatAtom(v reflect.Value) string {
    switch v.Kind() {
    case reflect.Invalid:
        return "Invalid"
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return strconv.FormatInt(v.Int(), 10)
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return strconv.FormatUint(v.Uint(), 10)
    // ... 浮點數和負數的分支省略了...
    case reflect.Bool:
        return strconv.FormatBool(v.Bool())
    case reflect.String:
        return strconv.Quote(v.String())
    case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
        return v.Type().String() + "0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
    default: // reflect.Array, reflect.Struct, reflect.Interface
        return v.Type().String() + " value"
    }
}

到目前爲止,這個函數把每一個值做爲一個沒有內部結構且不可分割的物體(因此函數名稱叫formatAtom)。對於聚合類型和接口,只輸出值的類型。對於引用類型,輸出類型和以十六進制表示的引用地址。這個結構仍然不夠理想,下一節會繼續改進。
由於 Kind 只關心底層實現,因此 format. Any 對命名類型的效果也很好:

var x int64 = 1
var d time.Duration = 1 * time.Nanosecond
fmt.Println(format.Any(x))                  // "1"
fmt.Println(format.Any(d))                  // "1"
fmt.Println(format.Any([]int64{x}))         // "[]int64 0x8202b87b0"
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"

Display:一個遞歸的值顯示器

接下來改善組合類型的顯示。此次再也不實現一個 fmt.Sprint,而是實現一個稱爲 Display 的調試工具函數,這個函數對給定的一個複雜值x,輸出這個複雜值的完整結構,並對找到的每一個元素標上這個元素的路徑。
應當儘可能避免在包的 API 裏暴露反射的相關內容,以後將定義一個未導出的函數 display 來作真正的遞歸處理,再暴露 Display,而 Display 則只是一個簡單的封裝:

func Display(name string, x interface{}) {
    fmt.Printf("Display %s (%T):\n", name, x)
    display(name, reflect.ValueOf(x))
}

在 display 中,使用以前定義的 formatAtom 函數來輸出基礎值,直接就把這個函數搬過來了。使用 reflect. Value 的一些方法來遞歸展現複雜類型的每一個組成部分。當遞歸深刻是,path 字符串會增加,表示是如何達到當前值的。
上兩節的示例都是在模擬實現 fmt.Sprint,結構都是經過 strconv 包轉成字符串而後返回的。這裏就直接使用 fmt 包簡化了部分邏輯:

package display

import (
    "fmt"
    "reflect"
    "strconv"
)

// formatAtom 格式化一個值,且不分析它的內部結構
func formatAtom(v reflect.Value) string {
    switch v.Kind() {
    case reflect.Invalid:
        return "invalid"
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return strconv.FormatInt(v.Int(), 10)
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return strconv.FormatUint(v.Uint(), 10)
    // ... 浮點數和負數的分支省略了...
    case reflect.Bool:
        if v.Bool() {
            return "true"
        }
        return "false"
    case reflect.String:
        return strconv.Quote(v.String())
    case reflect.Chan, reflect.Func, reflect.Ptr,
        reflect.Slice, reflect.Map:
        return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
    default: // reflect.Array, reflect.Struct, reflect.Interface
        return v.Type().String() + " value"
    }
}

func Display(name string, x interface{}) {
    fmt.Printf("Display %s (%T):\n", name, x)
    display(name, reflect.ValueOf(x))
}

func display(path string, v reflect.Value) {
    switch v.Kind() {
    case reflect.Invalid:
        fmt.Printf("%s = invalid\n", path)
    case reflect.Slice, reflect.Array:
        for i := 0; i < v.Len(); i++ {
            display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
        }
    case reflect.Struct:
        for i := 0; i < v.NumField(); i++ {
            fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
            display(fieldPath, v.Field(i))
        }
    case reflect.Map:
        for _, key := range v.MapKeys() {
            display(fmt.Sprintf("%s[%s]", path,
                formatAtom(key)), v.MapIndex(key))
        }
    case reflect.Ptr:
        if v.IsNil() {
            fmt.Printf("%s = nil\n", path)
        } else {
            display(fmt.Sprintf("(*%s)", path), v.Elem())
        }
    case reflect.Interface:
        if v.IsNil() {
            fmt.Printf("%s = nil\n", path)
        } else {
            fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
            display(path+".value", v.Elem())
        }
    default: // 基本類型、通道、函數
        fmt.Printf("%s = %s\n", path, formatAtom(v))
    }
}

分析類型分支

接下來對 display 函數裏的類型分支逐一進行分析。

slice與數組
二者的邏輯一致。Len 方法返回元素的個數,Index(i) 會返回第 i 個元素,返回的元素的類型爲 reflect.Value(若是i越界會崩潰)。這兩個方法與內置的 len(a) 和 a[i] 序列操做類型。在每一個序列上遞歸調用了 display 函數,只是在路徑後追加了 "[i]"。
儘管 reflect.Value 有不少方法,但對於每一個值,只有少許的方法能夠安全調用。好比,Index 方法能夠在 Slice、Arrar、String 類型的值上安全調用,但對於其餘類型則會崩潰。

結構體
NumField 方法能夠報告結構中的字段數,Field(i) 會返回第 i 個字段,返回的字段類型爲 reflect.Value。字段列表包括了從匿名字段中作了類型提高的字段。
v.Field(i) 是第i個字段的值,v.Type().Field(i) 就是第i個字段的名稱,而後再 .name 就是名稱的字符串類型。

map
MapKeys 方法返回一個元素類型爲 reflect.Value 的 slice,每一個元素都是一個 map 的 key。與日常遍歷 map 的結果相似,順序是不固定的。MapIndex(key) 返回 key 對應的值。這裏仍是忽略了一些情形,map 的 key 也多是超出 formatAtom 能處理的合法類型,好比數組、結構體、接口均可以是合法的key。這還須要再修改一點代碼,這裏就沒有作。

指針
Elem 方法返回指針指向的變量,一樣也是以 reflect.Value 類型返回。這個方法在指針是 nil 時也能正確處理,但返回的結果屬於 Invalid 類型,因此用了 IsNil 來顯式檢測空指針,方便輸出一條合適的消息。爲了不歧義,在路徑前加了 * 外邊再套一層圓括號。

接口
再次使用 IsNil 來判斷接口是否爲空。而後用 v.Elem() 獲取接口的動態值。再打印出對應的類型的值。

實際使用

如今 Display 已經完成了,立刻就來實際使用一下。使用下面的這樣一個複雜的結構體來進行驗證:

package main

import "gopl/ch12/display"

type Movie struct {
    Title, Subtitle string
    Year            int
    Color           bool
    Actor           map[string]string
    Oscars          []string
    Sequel          *string
}

func main() {
    strangelove := Movie{
        Title:    "Dr. Strangelove",
        Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
        Year:     1964,
        Color:    false,
        Actor: map[string]string{
            "Dr. Strangelove":            "Peter Sellers",
            "Grp. Capt. Lionel Mandrake": "Peter Sellers",
            "Pres. Merkin Muffley":       "Peter Sellers",
            "Gen. Buck Turgidson":        "George C. Scott",
            "Brig. Gen. Jack D. Ripper":  "Sterling Hayden",
            `Maj. T.J. "King" Kong`:      "Slim Pickens",
        },

        Oscars: []string{
            "Best Actor (Nomin.)",
            "Best Adapted Screenplay (Nomin.)",
            "Best Director (Nomin.)",
            "Best Picture (Nomin.)",
        },
    }

    display.Display("strangelove", strangelove)
}

執行後輸出以下:

PS G:\Steed\Documents\Go\src\gopl\ch12\desplay_demo> go run main.go
Display strangelove (main.Movie):
strangelove.Title = "Dr. Strangelove"
strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb"
strangelove.Year = 1964
strangelove.Color = false
strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott"
strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden"
strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens"
strangelove.Actor["Dr. Strangelove"] = "Peter Sellers"
strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers"
strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers"
strangelove.Oscars[0] = "Best Actor (Nomin.)"
strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)"
strangelove.Oscars[2] = "Best Director (Nomin.)"
strangelove.Oscars[3] = "Best Picture (Nomin.)"
strangelove.Sequel = nil
PS G:\Steed\Documents\Go\src\gopl\ch12\desplay_demo>

調用標準庫的內部結構
還可使用 Display 來顯示標準庫類型的內部結構,好比: *os.File:

display.Display("os.Stderr", os.Stderr)

注意,即便是非導出的字段在反射下也是可見的。
還能夠把 Display 做用在 reflect.Value 上,而且觀察它如何遍歷 *os.File 的類型描述符的內部結構:

display.Display("rV", reflect.ValueOf(os.Stderr))

調用指針
這裏注意以下兩個例子的差別:

var i interface{} = 3

display.Display("i", i)
// 輸出:
// Display i (int):
// i = 3

display.Display("&i", &i)
// 輸出:
// Display &i (*interface {}):
// (*&i).type = int
// (*&i).value = 3

在第一個例子中,Display 調用 reflect.ValueOf(i),返回值的類型爲 int。
在第二個例子中,Display 調用 reflect.ValueOf(&i),返回的類型爲 Ptr,而且是一個指向i的指針。在 Display 的 Ptr 分支中,會調用 Elem 方法,返回一個表明變量 i 的 Value,其類型爲 Interface。相似這種間接得到的 Value 能夠表明任何值,包括這裏的接口。這是 display 函數遞歸調用本身,輸出接口的動態類型和動態值。

循環引用

在當前的這個實現中,Display 在對象圖中存在循環引用時不會自行終止。好比出差一個首尾相連的鏈表:

// 一個指向本身的結構體
type Cycle struct{ Value int; Tail *Cycle }
var c Cycle
c = Cycle{42, &c}
display.Display("c", c)

執行後會輸出一個持續增加的展開式:

Display c (main.Cycle):
c.Value = 42
(*c.Tail).Value = 42
(*(*c.Tail).Tail).Value = 42
(*(*(*c.Tail).Tail).Tail).Value = 42
(*(*(*(*c.Tail).Tail).Tail).Tail).Value = 42

不少 Go 程序都會包含一些循環引用的數據。讓 Display 支持這類成環的數據結構須要些技巧,須要額外記錄迄今訪問的路徑,相應會帶來成本。
一個通用的解決方案須要 unsafe 語言特性,在以後的 unsafe 包的示例中,會有對循環引用的處理。

還有一個相對比較容易實現的思路,限制遞歸的層數。這個不是那麼通用,也不是很完美。可是不須要藉助 unsafe 就能夠實現。

循環引用在 fmt.Sprint 中不構成一個大問題,由於它不多嘗試輸出整個結構體。好比,當遇到一個指針時,就只簡單地輸出指針的數字值,這樣就不是引用了。但若是遇到一個 slice 或 map 包含自身,它仍是會卡住,只是不值得爲了這種罕見的案例而去承擔處理循環引用的成本。

示例:編碼 S表達式

Display 如今能夠做爲一個顯示結構化數據的調試工具,只要再稍加修改,就能夠用它來對任意 Go 對象進行編碼或編排,使之成爲適用於進程間通訊的消息。
Go 的標準庫已經支持了各類格式,包括:JSON、XML、ASN.1。另外還有一種普遍使用的格式是 Lisp 語言中的 S表達式。與其餘格式不一樣的是 S表達式還沒被 Go 標準庫支持,主要是由於它沒有一個公認的標準規範。
接下來就要定義一個包用於將任意的 Go 對象編碼爲 S表達式,它須要支持如下的結構:

42          integer
"hello"     string (帶有Go風格的引號)
foo         symbol (未用引號括起來的名字)
(1 2 3)     list   (括號包起來的0個或多個元素)

布爾值通常用符號 t 表示真,用空列表 () 或者符號 nil 表示假,但爲了簡化,這裏的實現直接忽略了布爾值。通道和函數也被忽略了,由於它們的狀態對於反射來講是不透明的。這裏的實現還忽略了實數、複數和接口。(部分實現能夠後續進行添加完善。)

編碼方式

將 Go 語言的類型編碼爲S表達式的方法以下:

  • 整數和字符串以顯而易見的方式編碼
  • 空值編碼爲nil符號
  • 數組和slice被編碼爲列表
  • 結構體編碼爲一個字段綁定(field binding)的列表,每一個字段綁定都是一個包含兩個元素的列表。
  • map也編碼爲鍵值對的列表。按照傳統,S表達式使用形式爲 (key . value) 的單個結構單元(cons cell)來表示key/value對。可是爲了簡化解碼過程,示例的實現中是沒有加 "." 的。

編碼器實現

編碼用單個遞歸調用函數 encode 來實現。它的結構上域上一節的 Display 在本質上是一致的:

package sexpr

import (
    "bytes"
    "fmt"
    "reflect"
)

func encode(buf *bytes.Buffer, v reflect.Value) error {
    switch v.Kind() {
    case reflect.Invalid:
        buf.WriteString("nil")
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        fmt.Fprintf(buf, "%d", v.Int())
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        fmt.Fprintf(buf, "%d", v.Uint())
    case reflect.String:
        fmt.Fprintf(buf, "%q", v.String())
    case reflect.Ptr:
        return encode(buf, v.Elem())
    case reflect.Array, reflect.Slice: // (value ...)
        buf.WriteByte('(')
        for i := 0; i < v.Len(); i++ {
            if i > 0 {
                buf.WriteByte(' ')
            }
            if err := encode(buf, v.Index(i)); err != nil {
                return err
            }
        }
        buf.WriteByte(')')
    case reflect.Struct: // ((name value) ...)
        buf.WriteByte('(')
        for i := 0; i < v.NumField(); i++ {
            if i > 0 {
                buf.WriteByte(' ')
            }
            fmt.Fprintf(buf, "(%s ", v.Type().Field(i).Name)
            if err := encode(buf, v.Field(i)); err != nil {
                return err
            }
            buf.WriteByte(')')
        }
        buf.WriteByte(')')
    case reflect.Map: // ((key value) ...)
        buf.WriteByte('(')
        for i, key := range v.MapKeys() {
            if i > 0 {
                buf.WriteByte(' ')
            }
            buf.WriteByte('(')
            if err := encode(buf, key); err != nil {
                return err
            }
            buf.WriteByte(' ')
            if err := encode(buf, v.MapIndex(key)); err != nil {
                return err
            }
            buf.WriteByte(')')
        }
        buf.WriteByte(')')
    default: // float, complex, bool, chan, func, interface
        return fmt.Errorf("unsupported type: %s", v.Type())
    }
    return nil
}

// Marshal 把 Go 的值編碼爲 S 表達式的形式
func Marshal(v interface{}) ([]byte, error) {
    buf := new(bytes.Buffer)
    if err := encode(buf, reflect.ValueOf(v)); err != nil {
        return nil, err
    }
    return buf.Bytes(), nil
}

Marshal 函數把上面的編碼器封裝成一個 API,它相似於其餘 encoding/... 包裏的 API。
繼續用上一節驗證 Display 的結構體來應用到這裏:

package main

import (
    "fmt"
    "gopl/ch12/sexpr"
    "os"
)

type Movie struct {
    Title, Subtitle string
    Year            int
    // Color           bool
    Actor           map[string]string
    Oscars          []string
    Sequel          *string
}

func main() {
    strangelove := Movie{
        Title:    "Dr. Strangelove",
        Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
        Year:     1964,
        // Color:    false,
        Actor: map[string]string{
            "Dr. Strangelove":            "Peter Sellers",
            "Grp. Capt. Lionel Mandrake": "Peter Sellers",
            "Pres. Merkin Muffley":       "Peter Sellers",
            "Gen. Buck Turgidson":        "George C. Scott",
            "Brig. Gen. Jack D. Ripper":  "Sterling Hayden",
            `Maj. T.J. "King" Kong`:      "Slim Pickens",
        },

        Oscars: []string{
            "Best Actor (Nomin.)",
            "Best Adapted Screenplay (Nomin.)",
            "Best Director (Nomin.)",
            "Best Picture (Nomin.)",
        },
    }

    b, err := sexpr.Marshal(strangelove)
    if err != nil {
        fmt.Fprintf(os.Stderr, "sexpr.Marshal err: %v", err)
    }
    fmt.Println(string(b))
}

因爲如今不支持布爾值,因此會返回錯誤:

PS H:\Go\src\gopl\ch12\sexpr_demo> go run main.go
sexpr.Marshal err: unsupported type: bool[]

去掉結構體和數據中的Color字段後就正常了:

PS H:\Go\src\gopl\ch12\sexpr_demo> go run main.go
((Title "Dr. Strangelove") (Subtitle "How I Learned to Stop Worrying and Love the Bomb") (Year 1964) (Actor (("Dr. Strangelove" "Peter Sellers") ("Grp. Capt. Lionel Mandrake" "Peter Sellers") ("Pres. Merkin Muffley" "Peter Sellers") ("Gen. Buck Turgidson" "George C. Scott") ("Brig. Gen. Jack D. Ripper" "Sterling Hayden") ("Maj. T.J. \"King\" Kong" "Slim Pickens"))) (Oscars ("Best Actor (Nomin.)" "Best Adapted Screenplay (Nomin.)" "Best Director (Nomin.)" "Best Picture (Nomin.)")) (Sequel nil))
PS H:\Go\src\gopl\ch12\sexpr_demo>

輸出的內容很是緊湊,不適合閱讀,不過做爲格式化的編碼已經實現了。若是要輸出一個帶縮進和換行的美化的格式,要從新實現一個 encode 函數。
與 fmt.Print、json.Marshal、Display 這些同樣,sexpr.Marshal 在遇到循環引用的數據時也會無限循環。

接下來還能夠繼續實現一個解碼器。不過在那以前,還要先了解一下如何用反射來更新程序中的變量。都在下一篇裏。

相關文章
相關標籤/搜索