第七章接口

接口类型是对其他类型行为的概括与抽象。通过使用接口,可以写出更加灵活和通用的函数,这些函数不用绑定在一个特定的类型实现上

Go语言的接口是隐式实现,对于一个具体的类型,无须声明它实现了哪些接口,只要提供接口所必需的方法即可。这种设计可以无需改变已有类型的实现,就可以为这些类型创建新的接口,对于那些不能修改包的类型,这一点特别有用

  • 接口即约定

具体类型指定了它所含数据的精确布局,同时暴露了基于这个精确布局的内部操作。比如对于数值有算术操作,对于slice类型有索引、append、range等操作。具体类型还会通过其方法来提供额外的能力-->只要知道了具体类型的数据,就精确地知道了它是什么以及它能干什么

接口类型是一种抽象类型,它并没有暴露所含数据的布局或者内部结构,也没有数据的哪些基本操作,提供的仅仅是一些方法。拿到一个接口类型的值,仅仅能知道它能做什么/它提供了哪些方法

fmt.Printf-->是把结果发到标准输出(标准输出就是一个文件)

fmt.Sprintf-->是把结果以string类型返回

package fmt
//Fprintf的前缀F指文件,表示格式化的输出会写入第一个实参所指代的文件
func Fprintf(w io.Writer, format string, ards ...interface{}) (int, error)

//对于Printf,第一个实参就是os.Stdout,它属于*os.File类型
func Printf(format string, args ...interface{}) (int, error) {
    return Fprintf(os.Stdout, format, args...)
}

//对于Sprintf,第一个实参模拟了一个文件,&buf就是一个指向内存缓冲区的指针,与文件类似,这个缓冲区也可以写入多个字节
func Sprintf(format string, args ...interface{}) string {
    var buf bytes.Buffer
    Fprintf(&buf, format, args...)
    return buf.String()
}

//Fprintf的第一个形参也不是文件类型,而是io.Writer接口类型
package io

//Writer接口封装了基础的写入方法
type Writer interface {
    //Write从p向底层数据流写入len(p)个字节的数据
    //返回实际写入的字节数(0 <= n <= len(p))
    //如果没写完,那么会返回遇到的错误
    //在Write返回n<len(p)时,err必须为非nil
    //Write不允许修改p的数据,即使是临时修改
    //实现时不允许残留p的引用
    Write(p []byte) (n int, err error)
}

io.Writer接口定义了Fprintf和调用者之间的约定。一方面,这个约定要求调用者提供的具体类型(比如*os.File或者*bytes.Buffer)包含一个与其签名和行为一致的Write方法。另一方面,这个约定保证了Fprintf能使用任何满足io.Writer接口的参数。Fprintf只需要能调用参数的Write函数,无须假设它写入的是一个文件还是一段内存

//*ByteCounter类型的方法仅仅统计传入数据的字节数

type ByteCounter int

func (c *ByteCounter) Write(p []byte) (int, error) {
    *c += ByteCounter(len(p))  //转换int为ByteCounter类型
    return len(p), nil
}

//因为*ByteCounter满足io.Writer接口的约定,所以可以在Fprintf中使用它,Fprintf察觉不到这种类型差异,ByteCounter也能正确地累积格式化后结果的长度

func main() {
    //!+main
    var c ByteCounter
    c.Write([]byte("hello"))
    fmt.Println(c) // "5"

    c = 0 // reset the counter
    var name = "Dolly"
    fmt.Fprintf(&c, "hello, %s", name)
    fmt.Println(c) // "12"
    //!-main
}

除了io.Writer之外,fmt包还有另一个重要的接口。Fprintf和Fprintln提供了一个让类型控制如何输出自己的机制。定义一个String方法就可以让类型满足广泛使用的接口fmt.Stringer

package fmt
//在字符串格式化时如果需要一个字符串,那么就调用这个方法来把当前值转化为字符串
//Print这种不带格式化参数的输出方式也是调用这个方法
type Stringer interface {
    String() string
}
  • 接口类型

一个接口类型定义了一套方法,如果一个具体类型要实现该接口,那么必须实现接口类型定义中的所有方法

package io

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

//另外可以通过组合已有接口得到新接口
type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}
//嵌入式接口,与嵌入式结构类似,让我们可以直接使用一个接口,而不用逐一写出这个接口所包含的方法
  • 实现接口

如果一个类型实现了一个接口要求的所有方法,那么这个类型实现了这个接口。比如,*os.File类型实现了io.Reader、Writer、Closer和ReaderWriter接口

*bytes.Buffer实现了Reader、Writer和ReaderWriter,但没有实现Closer,因为它没有close方法

//接口的赋值规则:仅当一个表达式实现了一个接口时,这个表达式才可以赋给该接口

var w io.Writer
w = os.Stdout  //OK:*os.File有Write方法
w = new(bytes.Buffer)  //OK:*bytes.Buffer有Write方法
w = time.Second  //编译错误:time.Duration缺少Write方法

一个类型有某一个方法的具体含义:

对每一个具体类型T,部分方法的接收者就是T,而其他方法的接收者则是*T指针

空接口类型:接口类型interface{}。因为空接口类型对其实现类型没有任何要求,所以可以把任何值赋给空接口类型

var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)

非空的接口类型(比如io.Writer)通常由一个指针类型来实现,特别是当接口类型的一个或多个方法暗示会修改接收者的情形(比如Write方法)。一个指向结构的指针才是最常见的方法接收者

从具体类型出发、提取其共性而得出的每一种分组方式都可以表示为一种接口类型。与基于类的语言(它们显式地声明了一个类型实现的所有接口)不同的是,在Go语言里我们可以在需要时才定义新的抽象和分组,并且不用修改原有类型的定义

  • 使用flag.Value来解析参数

如何使用标准接口flag.Value来帮助我们定义命令行标志

var period = flag.Duration("period", 1*time.Second, "sleep period")

func main() {
    flag.Parse()
    fmt.Printf("Sleeping for %v...", *period)
    time.Sleep(*period)
    fmt.Println()
}
//flag.Value类型的接口
package flag

//Value接口代表了存储在标志内的值
type Value interface {
    //String方法用于格式化标志对应的值,可用于输出命令行帮助信息。由于有了该方法,因此每个flag.Value其实也是fmt.Stringer。Set方法解析了传入的字符串参数并更新标志值
    String() string  
    Set(string) error
}
  • 接口值

一个接口类型的值有两个部分:一个具体类型和该类型的一个值,二者称为接口的动态类型和动态值

接口值可以用==和!=操作符来比较。如果两个接口值都是nil或者二者的动态类型完全一致且二者动态值相等(使用动态类型的==操作符来做比较),那么两个接口值相等。因为接口值是可以比较的,所以它们可以作为map的键,也可以作为switch语句的操作数

需要注意,在比较两个接口值时,如果两个接口值的动态类型一致,但对应的动态值是不可比较的(比如slice),那么这个比较会以崩溃的方式失败

一般来讲,在编译时我们无法知道一个接口值的动态类型会是什么,所以通过接口来做调用时必然需要使用动态分发。编译器必须生成一段代码来从类型描述符拿到名为write的方法地址,再间接调用该方法地址。调用的接收者就是接口的动态值,即os.Stdout

含有空指针的非空接口:空的接口值(其中不包含任何信息)与仅仅动态值为nil的接口值是不一样的

const debug = true

func main() {
    var buf *bytes.Buffer
    if debug {
        buf = new(bytes.Buffer)
    }
    f(buf)  
    if debug {
         //...使用buf...
    }
}

//如果out不是nil
func f(out io.Writer) {
    //其他代码
    if out != nil {
        out.Write([]byte("done!
"))
    }
}

//当设置debug为true时,主函数收集函数f的输出到一个缓冲区中
//当设置debug为false时,会导致程序在调用out.Write时崩溃
if out != nil {
    out.Write([]byte("done!
"))  //宕机:对空指针取引用值
}

//当main函数调用f时,它把一个类型为*bytes.Buffer的空指针赋给了out参数,所以out的动态值确实为空。但它的动态类型是*bytes.Buffer,这表示out是一个包含空指针的非空接口,所以防御性检查out!=nil仍然是true

//动态分发机制决定了我们肯定会调用(*bytes.Buffer).Write,只不过这次接收者值为空
  • 使用sort.Interface来排序

sort包提供了针对任意序列根据任意排序函数原地排序的功能。在很多语言中,排序算法跟序列数据类型绑定,排序算法跟序列元素类型绑定。Go语言的sort.Sort函数对序列和其中元素的布局无任何要求,它使用sort.Interface接口来指定通用排序算法和每个具体的序列类型之间的协议。这个接口的实现确定了序列的具体布局,以及元素期望的排序方式

//一个原地排序算法需要知道三个信息:序列长度、比较两个元素的含义以及如何交换两个元素,所以sort.Interface接口就有三个方法

package sort

type Interface interface {
    Len() int
    Less(i, j int) bool  //i, j是序列元素的下标
    Swap(i, j int)
}

//要对序列排序,需要先确定一个实现了如上三个方法的类型,接着把sort.Sort函数应用到上面这类方法的实例上
//字符串slice
type StringSlice []string

func (p StringSlice) Len() int {return len(p)}
func (p StringSlice) Less(i, j int) bool {return p[i] < p[j]}
func (p StringSlice) Swap(i, j int) {p[i], p[j] = p[j], p[I]}

//现在就可以对一个字符串slice进行排序,只须简单地把一个slice转换为StringSlice类型即可
sort.Sort(StringSlice(names))
//类型转换生成了一个新的slice,与原始的names有同样的长度、容量和底层数组,不同的是额外增加了三个用于排序的方法
//sort包提供了StringSlice类型,以及一个直接排序的Strings函数,上面代码可以简写为sort.Strings(names)
//这种技术可以方便地复用到其他排序方式,比如忽略大小写或者特殊字符。对于更复杂的排序,也可以使用同样的思路,只用加上更复杂的数据结构和更复杂的sort.Interface方法实现
type Track struct {
    Title string
    Artist string
    Album string
    Year int
    Length time.Duration
}

var tracks = []*Track{
{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
{"Go", "Moby", "Moby", 1992, length("3m37s")},
{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
{"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
}
func length(s string) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
panic(s)
}
return d
}

 sort包提供了一个Reverse函数,可以把任意的排序反向。sort.Reverse函数使用了一个重要概念:组合(通过结构体内嵌组成类型)。sort包定义了一个未导出的类型reverse,这个类型是一个嵌入了sort.Interface的结构。reverse的Less方法直接调用了内嵌的sort.Interface值的Less方法,只交换传入的下标,就可以颠倒排序的结果

package sort

type reverse struct { Interface } //that is, sort.Interface

func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }

func Reverse(data Interface) Interface { return reverse{data} }

//reverse的另外两个方法Len和Swap,由内嵌的sort.Interface隐式提供。导出的函数Reverse则返回一个包含原始sort.Interface值的reverse实例
type customSort struct {
    t                []*Track
    less            func(x, y *Track) bool
}

func (x customSort) Len() int { return len(x.t) }
func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j])}
func (x customSort) Swap(i, j int) { x.t[I], x.t[j] = x.t[j], x.t[I]}

//多层比较函数,先按照标题Title排序,接着是年份Year,最后是时长Length
//匿名函数
sort.Sort(customSort{tracks, func(x, y *Track) bool{
    if x.Title != y.Title {
        return x.Title < y.Title
    }
    if x.Year != y.Year {
        return x.Year < y.Year
    }
    if x.Length != y.Length {
        return x.Length < y.Length
    }
    return false
}})

对一个长度为n的序列进行排序需要O(nlogn)次比较操作,而判断一个序列是否已经排好序则只需最多(n-1)次比较。sort包提供的IsSorted函数就可以做这个判断。与sort.Sort类似,它使用sort.Interface来抽象序列及其排序函数,只是从不调用Swap方法而已

values := []int{3, 1, 4, 1}
fmt.Println(sort.IntsAreSorted(values))  //"false"
sort.Ints(values)
fmt.Println(values)  //"[1 1 3 4]"
fmt.Println(sort.IntsAreSorted(values))  //"true"
sort.Sort(sort.Reverse(sort.IntSlice(values)))
fmt.Println(values)  //"[4 3 1 1]"
fmt.Println(sort.IntsAreSorted(values)) //"false"
  • http.Handler接口

net/http

package http

type Handler interface {
    ServeHTTP(w ResponseWriter, t *Request)
}

func ListenAndServe(address string, h Handler) error
//ListenAndServe函数需要一个服务器地址,比如"localhost:8000",以及一个Handler接口
//的实例(用来接受所有的请求)。这个函数会一直运行,直到服务出错(或者启动时就失败
//了)时返回一个非空的错误

func main() {
    db := database{"shoes": 50, "socks": 5}
    log.Fatal(http.ListenAndServe("localhost:8000", db))
}

type dollars float32

func (d dollars) String() string {
    return fmt.Sprintf("$%.2f", d)
}

type database map[string]dollars

func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    for item, price := range db {
         fmt.Fprintf(w, "%s: %s
", item, price)
    }
}
//把现有功能的URL设为/list,再加上另外一个/price用来显示单个商品的价格,商品可以在
//请求参数中指定,比如/price?item=socks
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    switch req.URL.Path{
    case "/list":
        for item, price := range db {
            fmt.Fprintf(w, "%s: %s
", item, price)
    case "/price":
        item := req.URL.Query().Get("item")
        price, ok := db[item]
        if !ok {
            w.WriteHeader(http.StatusNotFound)  //404
            fmt.Fprintf(w, "no such item: %q
", item)
            return
        }
        fmt.Fprintf(w, "%s
", price)
    default:
            w.WriteHeader(http.StatusNotFound)  //404
            fmt.Fprintf(w, "no such page: %s
", req.URL)
        }
    }
}
//下面的程序创建了一个ServeMux,用于将/list、/price这样的URL和对应的处理程序
//关联起来,这些处理程序也已经拆分到不同的方法中。最后作为主处理程序
//在ListenAndServe调用中使用这个ServerMux

func main() {
    db := database{"shoes": 50, "socks": 5}
    mux := http.NewServeMux()
    mux.Handle("/list", http.HandlerFunc(db.list))
    mux.Handle("/price", http.HandlerFunc(db.price))
    log.Fatal(http.ListenAndServe("localhost:8000", mux))
}

type database map[string]dollars

func (db database) list(w http.ResponseWriter, req *http.Request) {
    for item, price := range db {
        fmt.Fprintf(w, "%s: %s
", item, price)
    }
}

func (db database) price(w http.ResponseWriter, req *http.Request) {
    item := req.URL.Query().Get("item")
    price, of := db[item]
    if !ok {
        w.WriteHeader(http.StatusNotFound)  //404
        fmt.Fprintf(w, "no such item: %q
", item)
        return
    }
    fmt.Fprintf(w, "%s
", price)
}
  • error接口

error是预定义类型,实际上它只是一个接口类型,包含一个返回错误消息的方法

type error interface {
    Error() string
}

//构造error最简单的方法是调用errors.New,它会返回一个包含指定的错误消息的新的error实例
//完整的error包只有如下4行代码
package errors

func New(text string) error { return &errorString{text} }

type errorString struct { text string }

func (e *errorString) Error() string { return e.text }

//底层的errorString类型是一个结构,而没有直接用字符串,主要是为了避免将来的布局变更。满足error接口的是*errorString指针,而不是原始的errorString,主要是为了让每次New分配的error实例都互不相等????

直接调用errors.New比较罕见,因为有一个更易用的封装函数fmt.Errorf,它还额外提供了字符串格式化功能
package fmt

import "errors"

func Errorf(format string, args ...interface{}) error {
return errors.New(Sprintf(format, args...))
}
  • 示例:表达式求值器
  • 类型断言

类型断言是一个作用在接口值上的操作,写出来类似于x.(T),其中x是一个接口类型的表达式,而T是一个类型(称为断言类型)类型断言会检查作为操作数的动态类型是否满足指定的断言类型

//如果断言类型T是一个具体类型,那么类型断言会检查x的动态类型是否就是T。如果检查成功,类型断言的结果就是x的动态值,类型当然就是T。类型断言就是用来从它的操作数中把具体类型的值提取出来的操作。如果检查失败,那么操作崩溃

var w io.Writer
w = os.Stdout
f := w.(*os.File)  //成功:f == os.Stdout
c := w.(*bytes.Buffer)  //崩溃:接口持有的是*os.File而不是*bytes.Buffer

//如果断言类型是一个接口类型,那么类型会检查x的动态类型是否满足T。如果检查成功,动态值并没有提取出来,结果仍然是一个接口值,接口值的类型和值部分也没有变更,只是结果的类型为接口类型T。类型断言是一个接口值表达式从一个接口类型变为拥有另外一套方法的接口类型(通常方法数量是增多),但保留了接口值中的动态类型和动态值部分

如下类型断言代码中,w和rw都持有os.Stdout,于是所有对应的动态类型都是*os.File,但w作为io.Writer仅暴露了文件的Write方法,而rw还暴露了它的Read方法
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter)//成功:*os.File有Read和Write方法

w = new(ByteCounter)
rw = w.(io.ReadWriter)//崩溃:*ByteCounter没有Read方法
//无论哪种类型作为断言类型,如果操作数是一个空接口值,类型断言都失败。很少需要从一个接口类型向一个要求更宽松的类型做类型断言,该宽松类型的接口方法比原类型的少,而且是其子集。因为除了在操作nil之外的情况下,在其他情况下这种操作与赋值一致?

w = rw //io.ReadWriter可以赋给io.Writer
w = rw.(io.Writer)//仅当rw=nil时失败

//我们经常无法确定一个接口值的动态类型,这时就需要检测它是否时某一个特定类型。如果类型断言出现在需要两个结果的赋值表达式中,那么断言不会再失败时崩溃,而是会多返回一个布尔型的返回值来指示断言是否成功

var w io.Writer = os.Stdout
f, ok = w.(*os.File) //成功:ok, f == os.Stdout
b, ok = w.(*bytes.Buffer) //失败:!ok, b == nil

//按照惯例,一般把第二个返回值赋给一个名为ok的变量。如果操作失败,ok为false,而第一个返回值为断言类型的零值
//ok返回值通常马上就用来决定下一步做什么
if f, ok := w.(*os.File);ok{
//...使用f...
}
//当类型断言的操作数是一个变量时,有时会看到返回值的名字与操作数变量名一致,原有的值就被新的返回值掩盖了
if w, ok := w.(*os.File); ok{
//...use W...
}
  • 使用类型断言来识别错误

考虑os包中的文件操作返回的错误集合,I/O会因为很多原因失败,但有三类原因通常必须单独处理:文件已存储(创建操作),文件没找到(读取操作)以及权限不足

os提供了三个帮助函数用来对错误进行分类

package os

func IsExist(err error) bool
func IsNotExist(err error) bool
func IsPermission(err error) bool

//用专门的类型来表示结构化的错误值。os包定义了一个PathError类型来表示在与一个文件路径相关的操作上发生错误(比如Open或者Delete)

package os

//PathError记录了错误以及错误相关的操作和文件路径
type PathError struct {
    Op         string
    Path       string
    Err         error
}

func (e *PathError) Error() string {
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

//PathError的Error方法指示拼接了所有的字段,而PathError的结构则保留了错误所有的底层信息。对于那些需要区分错误的客户端,可以使用类型断言来检查错误的特定类型,这些类型包含的细节远远多于一个简单的字符串

_, err := os.Open("/no/such/file")
fmt.Println(err)  // "open/no/such/file: No such file or directory"
fmt.Printf("%#v
", err)
// &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2}
import (
    "errors"
    "syscall"
)

var ErrNotExists = errors.New("file does not exist")

//IsNotExist返回一个布尔值,该值表明错误是否代表文件或目录不存在
//report that a file or directory does not exist. It is satisfied by ErrNotExist 和其他
//一些系统调用错误会返回true

func IsNotExist(err error) bool {
if pe, ok := err.(*PathError); ok {
err = pe.Err
}
return err == syscall.ENOENT || err == ErrNotExist
}

//实际使用情况如下
_, err := os.Open("/no/such/file")
fmt.Println(os.IsNotExist(err))//"true"

//如果错误消息已被fmt.Errorf这类的方法合并到一个大字符串中,那么PathError的结构信息就丢失了
  • 通过接口类型断言来查询特性

net/http

func writeHeader(w io.Writer, contentType string) error {
    if _, err := w.Write([]byte("Content-Type: ")); err != nil {
        return err
    }
    if _, err := w.Write([]byte(contentType)); err != nil {
        return err
    }
    //......
}
//因为Write方法需要一个字节slice,而我们想写入的是一个字符串,所以[]byte(...)转换就是必需的。这种转换需要进行内存分配和内存复制,但复制后的内存又会被马上抛弃

//如何避免内存复制?

//writeString将s写入w
//如果w有WriteString方法,那么将直接调用该方法
func writeString(w io.Writer, s string) (n int, err error) {
    type stringWriter interface {
        WriteString(string) (n int, err error)
    }
    if sw, ok := w.(stringWriter); ok{
        return sw.WriteString(s)  //避免了内存复制
    }
    return w.Write([]byte(s)) //分配了临时内存
}

func writeHeader(w io.Writer, contentType string) error {
    if _, err := writeString(w, "Content-Type: "); err != nil {
        return err
    }
    if _, err := writeString(w, contentType); err!=nil {
        return err
    }
    //......
}

为了避免代码重复,把检查挪到了工具函数writeString中。实际上,标准库提供了io.WriteString,而且这也是向io.Writer写入字符串的推荐方法

  • 类型分支

接口有两种不同的风格。第一种风格下,典型的比如io.Reader、io.Writer、fmt.Stringer、sort.Interface、http.Handler和error,接口上的各种方法突出了满足这个接口的具体类型之间的相似性,但隐藏了各个具体类型的布局和各自特有的功能。这种风格强调了方法,而不是具体类型

第二种风格则充分利用了接口值能够容纳各种具体类型的能力,它把接口作为这些类型的联合来使用。类型断言用来在运行时区分这些类型并分别处理。在这种风格中,强调的是满足这个接口的具体类型,而不是这个接口的方法,也不注重信息隐藏。这种风格的接口使用方式称为可识别联合

Go语言的数据库SQL查询API也允许我们干净地分离查询中的不变部分和可变部分

import "database/sql"

func listTracks(db sql.DB, artist string, minYear, maxYear int) {
    result, err := db.Exec(
        "SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?",
        artist, minYear, maxYear)
    //......
}

//Exec方法把查询字符串中的每一个"?"都替换为与相应参数值对应的SQL字面量,这些参数可能是布尔型、数字、字符串或者nil。通过这种方式构造请求可以帮助避免SQL注入攻击,攻击者可以通过在输入数据中加入不恰当的引号来控制查询。在Exec的实现代码中,有一个类似如下的函数,将每个参数值转为对应的SQL字面量
func sqlQuote(x interface{}) string {
    if x == nil {
        return "NULL"
    } else if _, ok := x.(int); ok {
        return fmt.Sprintf("%d", x)
    } else if _, ok := x.(uint); ok {
        return fmt.Sprintf("%d", x)
    } else if b, ok := x.(bool); ok {
        if b {
            return "TRUE"
        }
        return "FALSE"
    } else if s, ok := x.(string); ok {
        return sqlQuoteString(s)  //(not shown)
    } else {
        panic(fmt.Sprintf("unexpected type %T: %v", x, x))
    }
}

//一个switch语句可以把包含一长串值相等比较的if-else语句简化掉。一个相似的类型分支语句则可以用来简化一长串的类型断言if-else语句
类型分支的最简单形式与普通分支语句类似,两个的差别是操作数改为x.(type)(注意:这里直接写关键字type而不是一个特定类型),每个分支是一个或者多个类型。类型分支的分支判定基于接口值的动态类型,其中nil分支需要x==nil,而default分支则在其他分支都没有满足时才运行
switch x.(type){
case nil:  //...
case int, uint:  //...
case bool:  //...
case string:  //...
default:  //...
}
//与普通的switch语句类似,分支是按顺序来判定的,当一个分支符合时,对应的代码会执行。分支的顺序在一个或多个分支是接口类型时会变得重要,因为有可能两个分支都能满足。default分支的位置是无关紧要的。类型分支不允许使用fallthrough

//在原来的代码中,bool和string分支的逻辑需要访问由类型断言提取出来的原始值。所以类型分支也有一种扩展形式,它用来把每个分支中提取出来的原始值绑定到一个新的变量
switch x := x.(type)
//这里把新的变量也命名为x,重用变量名
//与switch语句类似,类型分支也隐式创建了一个词法块,所以声明一个新变量叫x并不与外部块中的变量x冲突。每个分支也会隐式创建各自的词法块

func sqlQuote(x interface{}) string {
    switch x := x.(type)
    case nil:
        return "NULL"
    case int, uint:
        return fmt.Sprintf("%d", x)  //这里x类型为interface{}
    case bool:
        if x {
            return "TRUE"
        }
        return "FALSE"
    case string:
        return sqlQuoteString(x)
    default:
        panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
  • 示例:基于标记的XML解析
  • 一些建议
原文地址:https://www.cnblogs.com/liushoudong/p/13064891.html