golang学习笔记6 | 青训营

57 阅读6分钟

接口值

从概念来说,一个接口类型的值(简称接口值)其实有两部分:一个具体类型和该类型的一个值。二者称为接口的动态类型和动态值。

  • golang中,类型仅仅是一个编译时的概念,所以类型不是一个值。在概念模型中,用类型描述符来提供每个类型的具体信息,比如它的名字和方法。
  • 接口值可以用==!=操作符来做比较。如果两个接口值都是nil或者二者的动态类型完全一致且二者动态值相等(使用动态类型的==操作符来做比较),那么两个接口值相等。因为接口值是可以比较的,所以它们可以作为map的键,也可以作为switch语句的操作数。
  • 在比较两个接口值时,如果两个接口值的动态类型一致,但对应的动态值是不可比较的,那么这个比较会以崩溃的方式失败。从这点看,接口类型是非平凡的。

注意:含有空指针的非空接口

空的接口值与仅仅动态值为nil的接口值是不一样的。

使用sort.Interface来排序

sort.Interface接口指定通用排序算法和每个具体的序列类型之间的协议。这个接口的实现确定了序列的具体布局(经常是一个slice),以及元素期望的排序方式。

  • 一个原地排序算法需要知道三个信息:序列长度、比较两个元素的含义以及如何交换两个元素,所以sort.Interface接口就有三个办法。
package sort
​
type Interface interface {
    Len() int
    Less(i, j int) bool // i, j是序列元素的下标
    Swap(i, j int)
}

要对序列排序,需要先确定一个实现了如上三个方法的类型,接着把sort.Sort函数应用到上面这类方法的实例上。

  • sort.Reverse使用了一个重要概念:组合。
package sort
​
type reverse struct { Interface } // that is, sort.Interfacefunc (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
​
func Reverse(data Interface) Interface { return reverse{ data } }

sort包定义了一个未导出的类型reverse,这个类型是一个嵌入了sort.Interface的结构。reverseLess方法直接调用了内嵌的sort.Interface值的Less方法,但只交换传入的下标,就可以颠倒排序的结果。

reverse的另外两个方法LenSwap,由内嵌的sort.Interface隐式提供。导出的函数Reverse则返回一个包含原始sort.Interface值的reverse实例。

http.Handler接口

作为服务端API基础的http.Handler接口。

package http
​
type Handler interface {
    ServeHTTP(W ResponseWriter, r *Request)
}
​
func ListenAndServe(address string, h Handler) error

ListenAndServe函数需要一个服务器地址,比如"localhost:8000",以及一个Handler接口的实例。

  • 普通处理URL
// req *http.Request
​
req.URL.Path
  • 普通地处理请求参数
// /xxx?item=valueswitch req.URL.Path{
    case "/xxx":
        item := req.URL.Query().Get("item")
        /*
            xxx
        */
}

URLQuery方法,把Http的请求参数解析为map,或者更精确来讲,解析为一个multimap,由net/url包的url.Values类型实现。

  • net/http包提供了一个请求多工转发器ServeMux,用来简化URL和处理程序之间的关联。
  • 满足同一个接口的多个类型是可以互相替代的,Web服务器可以把请求分发到任意一个http.Handler
  • 对于一个更复杂的应用,多个ServeMux会组合起来,用来处理更复杂的分发需求。

ServeMux例子

mux := http.NewServeMux()
mux.Handle(URL1 string, http.HandlerFunc(mothod.func1))
max.Handle(URL2 string, http.HandlerFunc(mothod.func2))
log.Fatal(http.ListenAndServe(ip_port string, mux))
​
func (md mothod) func1(w http.ResponseWriter, req *http.Request) {
    /*
        xxx
    */
}
​
func (md mothod) func2(w http.ResponseWriter, req *http.Request) {
    /*
        xxx
    */
}

表达式http.HandlerFunc(xxx.func)其实是类型转换,而不是函数调用。注意,http.HandlerFunc是一个类型。

package http
​
type HandlerFunc func(w ResponseWriter, r *Request)
​
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}
  • net/http包提供一个全局的ServeMux实例DefaultServeMux,以及包级别的注册函数http.Handlehttp.HandlerFunc。要让DefaultServeMux作为服务器的主处理程序,无须把它传给ListenAndServe,直接传nil即可。

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实例都互不相等。
fmt.Println(errors.New("EOF") == errors.New("EOF"))     // false
  • 直接调用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

  • 如果断言类型T是一个接口类型,那么类型断言检查x的动态类型是否满足T。如果检查成功,动态值并没有提取出来,结果仍然是一个接口值,接口值的类型和值部分也没有变更,只是结果的类型为接口类型T
  • 如果类型断言出现在需要两个结果的赋值表达式中,那么断言不会在失败时崩溃,而是会多返回一个布尔型的返回值来指示断言是否成功。
if w, ok := w.(*os.File); ok {
    // ...use w...
}

返回值的名字与操作数变量名一致,原有的值就被新的返回值掩盖了。

使用类型断言来识别错误

os包中有三类原因通常必须单独处理:文件已存储(创建操作),文件没找到(读取操作)以及权限不足。

package os 
​
func IsExist(err error) bool
func IsNotExist(err error) bool
func IsPermission(err error) bool

通过接口类型断言来查询特征

给一个特定类型多定义一个方法,就隐式地接受了一个特定约定。

类型分支

接口有两种不同的风格。

  1. 接口上的各种方法突出了满足这个接口的具体类型之间的相似性,但隐藏了各个具体类型的布局和各自特有的功能。这种风格强调了方法,而不是具体类型。
  2. 充分利用·了接口值能够容纳各种具体类型的能力,它把接口作为这些类型的联合(union)来使用。这种风格的接口使用方式称为可识别联合