1.接口即约定
接口是一种抽象类型,不暴露所含数据的布局或者内部结构和基本操作,只提供一些方法而已。
例子:fmt.Printf和fmt.Sprintf都封装了fmt.Fprintf
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
func Sprintf(format string, a ...interface{}) string {
p := newPrinter()
p.doPrintf(format, a)
s := string(p.buf)
p.free()
return s
}
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
因为fmt.Fprintf仅依赖于io.Writer接口所约定的方法,对参数的具体类型没有要求,所以我们可以用任何满足io.Writer接口的具体类型作为fmt.Fprintf的实参。 这种,可以吧一种类型替换为满足同一接口的另一种类型的特征,称为可取代性
如下进行测试,Write仅仅统计传入数据的字节数。因为 *ByteCounter满足io.Writer的约定,所以可在Fprintf中使用,Fprintf并未察觉这种类型的差异,同时ByteCounter也能正确地鸡肋格式化后结果的长度。
import "fmt"
type ByteCounter int
func ( c *ByteCounter)Write(p []byte) (int, error) {
*c += ByteCounter(len(p))
return len(p), nil
}
func main() {
var c ByteCounter
c.Write([]byte("hello"))
fmt.Println(c)// 5
c = 0
var name = "Dolly"
fmt.Fprintf(&c, "hello, %s", name )
fmt.Println(c) // 12
}
2.接口类型
要实现接口,必须实现接口类型定义中的所有方法
举例说,Reader抽象了所有可以读取字节的类型,Closer抽象了所有可以关闭的类型。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
还可以通过组合已有接口,得到新的接口:
type ReadCloser interface {
Reader
Closer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
这种语法称为嵌入式接口。也可以不用嵌入式接口声明:
type ReadWriter interface {
Read(p []byte)(n int, err error)
Write(p []byte)(n int, err error)
}
也可以混合:
type ReadWriter interface {
Read(p []byte)(n int, err error)
Writer
}
3.实现接口
只有一个类型实现了这个接口要求的所有方法,这个类型才算实现了这个接口。
var rwc io.ReadWriteCloser
rwc = os.Stdout // *os.File有Read、Write、Close方法
rwc = new(bytes.Buffer) // 错误, *bytes.Buffer缺少Close方法
接口封装了对应的类型和数据,只有通过接口暴露的方法才可以调用,类型的其它方法则无法通过接口来调用:
func main() {
os.Stdout.Write([]byte("hello")) // 正确,*os.File有Write方法
os.Stdout.Close() // 正确, *os.File有Close方法
var w io.Writer
w = os.Stdout
w.Write([]byte("hello")) // 正确,io.Writer有Write方法
w.Close() // 错误,io.Writer没有Close方法
}
空接口类型的作用:对其实现类没有任何要求,可以把任何值赋给空接口类型:
func main() {
var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one":1}
any = new(bytes.Buffer)
}
像下面这种形式,就断言了*byte.Buffer类型的一个值必然实现了io.Writer:
var w io.Writer = new(byte.Buffer)
如果不想引用w而又想实现接口:
var _ io.Writer = (*byte.Buffer)(nil)
一个具体类型可以实现很多不相关的接口,那么它可以定义如下类型:
Album
Book
Movie
Magazine
Podcast
TVEposide
Track
可以把所有商品都具备的,用一种接口类型来表示,比如:标题,创建日期,创建者列表:
type Artifact interface {
Title() string
Creators() []string
Created() time.Time
}
其他接口则限定于特定类型:
type Text interface {
Pages() int
Words() int // 字数属性只与书和杂志有关
PageSize() int
}
type Audio interface {
Stream() (io.ReadCloser, error)
RunningTime() time.Duration
Format() string // 比如mp3..
}
type Video interface {
Stream() (io.ReadCloser, error)
RunningTime() time.Duration
Format() string // 比如mp4,mkv..
Resolution() (x, y int) // 分辨率
}
如果要把Audio和Video按照同样的方式来处理,就可以定义一个Streamer接口来呈现它们的共性,而不用修改现有的类型定义:
type Streamer interface {
Stream() (io.ReadCloser, error)
RunningTime() time.Duration
Format() string .
}
4.使用flag.Value解析参数
flag.Value帮助我们定义命令行标识
下面程序实现了睡眠指定时间的功能:
import (
"time"
"flag"
"fmt"
)
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.Duration 函数创建了一个time.Duration类型的标志变量,允许用户来指定时长。这种设计提供了良好的接口。
在flag包内,Value如下定义:
type Value interface {
String() string
Set(string) error
} 其中String方法用于格式化标志对应的值,可用于在命令行输出帮助信息。Set方法解析了传入的字符串参数并更新标志值。
下面定义celsiusFlag类型,允许在参数中使用摄氏或华氏温度。在celsiusFlag中内嵌的Celsius类型已经有String方法了,所以只需再定义一个Set方法。
import (
"flag"
"fmt"
)
type Fahrenheit float64
type Celsius float64
func CToF (c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f-32)*5/9) }
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
type celsiusFlag struct{ Celsius }
func (f *celsiusFlag) Set(s string) error {
var uint string
var value float64
fmt.Sscanf(s, "%f%s", &value, &uint) // 无需检查错误
switch uint {
case "C", "`C":
f.Celsius = Celsius(value)
return nil
case "F", "`F":
f.Celsius = FToC(Fahrenheit(value))
return nil
}
return fmt.Errorf("无效温度 %q", s)
}
func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
f := celsiusFlag{value}
flag.CommandLine.Var(&f, name, usage)
return &f.Celsius
}
var temp = CelsiusFlag("temp", 20.0, "the temperature")
func main() {
flag.Parse()
fmt.Println(*temp)
}
调用Var方法会把*celsiusFlag的实参赋给flag.Value形参,编译器会检查 *celsiusFlag类型是否有flag.Value所必须的方法

5.使用sort.Interface来排序
在Go中,使用sort.Interface接口来指定通用排序算法和每个具体的序列类型之间的协议。这个接口的实现确定了序列的具体布局和元素期望的排序方式。
一个原地排序算法需要三个信息:序列长度,比较两个元素,交换两个元素。所以sort.Interface接口有这三个方法:
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
考虑字符串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] }
此时实现了Interface,可以进行简单的排序:
import (
"sort"
"fmt"
)
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] }
func main() {
var a = []string{"a","c","z","f","e"}
sort.Sort(StringSlice(a))
fmt.Println(a) // [a c e f z]
}
另一个例子
下面 tracks是个播放列表:
import (
"sort"
"fmt"
"time"
)
type Track struct {
Titie 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
}
下面的printTracks将播放列表输出为一个表格。
func printTracks(tracks []*Track) {
const format = "%v\t%v\t%v\t%v\t%v\t\n"
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
for _, t := range tracks {
fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
}
tw.Flush()
}
要按照Artist字段来对播放列表排序,需要先定义一个新的slice类型,以及必备的三个方法
type byArtist []*Track
func (x byArtist) Len() int { return len(x) }
func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist }
func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
测试:
func main() {
fmt.Println("byArtist:")
sort.Sort(byArtist(tracks))
printTracks(tracks)
}
输出:
byArtist:
Title Artist Album Year Length
----- ------ ----- ---- ------
Go Ahead Alicia Keys As I Am 2007 4m36s
Go Delilah From the Roots Up 2012 3m38s
Ready 2 Go Martin Solveig Smash 2011 4m24s
Go Moby Moby 1992 3m37s
也可以“按照艺术家排序”,可以直接反序:
func main() {
fmt.Println("\nReverse(byArtist):")
sort.Sort(sort.Reverse(byArtist(tracks)))
printTracks(tracks)
}
输出:
Reverse(byArtist):
Title Artist Album Year Length
----- ------ ----- ---- ------
Go Moby Moby 1992 3m37s
Ready 2 Go Martin Solveig Smash 2011 4m24s
Go Delilah From the Roots Up 2012 3m38s
Go Ahead Alicia Keys As I Am 2007 4m36s
这个sort.Reverse:
type reverse struct {
// This embedded Interface permits Reverse to use the methods of
// another Interface implementation.
Interface
}
// Less returns the opposite of the embedded implementation's Less method.
func (r reverse) Less(i, j int) bool {
return r.Interface.Less(j, i)
}
// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
return &reverse{data}
}
可以定义byYear,按照其他类型进行排序:
type byYear []*Track
func (x byYear) Len() int { return len(x) }
func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year }
func (x byYear) Swap (i, j int) { x[i], x[j] = x[j],x[i] }
测试:
func main() {
sort.Sort(byYear(tracks))
printTracks(tracks)
}
输出:
Title Artist Album Year Length
----- ------ ----- ---- ------
Go Moby Moby 1992 3m37s
Go Ahead Alicia Keys As I Am 2007 4m36s
Ready 2 Go Martin Solveig Smash 2011 4m24s
Go Delilah From the Roots Up 2012 3m38s
上面每个Len和Swap都一样。
下面具体类型customSort组合了一个slice和一个函数,写一个比较函数可以定义一个新的排序。
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:
func main() {
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
}})
printTracks(tracks)
}
输出:
Title Artist Album Year Length
----- ------ ----- ---- ------
Go Moby Moby 1992 3m37s
Go Delilah From the Roots Up 2012 3m38s
Go Ahead Alicia Keys As I Am 2007 4m36s
Ready 2 Go Martin Solveig Smash 2011 4m24s
对长度为n的序列进行排序需要O(nlogn)次比较操作,而判断排好序只要比较n-1次,sort包提供的IsSorted函数可以做这个判断。与sort.Sort类似,它使用sort.Interface来抽象序列及其排序函数,只是从来都不会调用Swap方法。
func main() {
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
}
6.http.Handler 接口
先看一下源码:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
ListenAndServe需要一个服务器地址,以及一个Handler接口的实例。
下面map类型代表仓库,存储商品和价格的映射,加上一个ServeHTTP方法满足http.Handler接口。遍历map并且输出其中的元素(TGPL94):
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\n", item, price)
}
}
另一个fetch程序(TGPL78):
func main() {
for _, url := range os.Args[1:] {
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
fmt.Printf("%s", b)
}
}
然后如图所示:


改进:上面这个只能列出所有商品并且不管URL。真实的服务器应该是定义不同的URL,每个触发不同的行为。
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) {
switch req.URL.Path {
case "/list" :
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
case "/price":
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
default:
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "no such page: %s\n", req.URL)
}
}
测试:
func main() {
db := database{"shoes": 50, "socks": 5}
log.Fatal(http.ListenAndServe("localhost:8000", db))
}
运行如图所示:


进一步:
真实的应用中,应当把每部分逻辑分到独立的函数或方法。某些相关的URL可能需要类似的逻辑,比如几个图片文件的URL可能都是/image/*.png形式。
Go中的net/http包提供了一个请求多工转发器ServeMux,用来简化URL和处理程序之间的关联。一个ServeMux把多个http.Handler组合成单个http.Handler。
下面代码,创建了一个ServeMux,将/list、/price这样的URL和对应的处理程序关联起来。最后主处理程序在ListenAndServe调用这个ServeMux:
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 dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
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\n", item, price)
}
}
func (db database) price (w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
}
这里的http.HandlerFunc(db.list)是一种类型转换,而不是函数调用。因为当调用db.list时,等价于以db为接收者调用database.list方法。所以说db.list是一个实现了处理功能的函数,而不是一个实例,因为它没有接口所需的方法,所以它不满足http.Handler接口,也不能传给mux.Handler
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
它就是一个让函数值满足接口的一个适配器,这种方式很常见,所以ServeMux引入了一种便捷方式:
mux.HandleFunc("/list", db.list)
mux.HandleFunc("/price", db.price)
同时net/http包提供一个全局的ServeMux实例DefaultServeMux,让DefaultServeMux作为服务器的主处理程序,无需传给ListenAndServe,直接传nil即可。
func main() {
db := database{"shoes": 50, "socks": 5}
http.HandleFunc("/list", db.list)
http.HandleFunc("/price", db.price)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
7.error接口
它只是一个接口类型,并不含一个返回错误消息的方法,源码如下:
type error interface{
Error() string
}
构造error最简单的方法是直接调用errors.New,完整的error包只有如下代码:
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
- 底层的errorString类型是一个结构,而没有直接用字符串,为了避免未来可能的布局变更。
- 满足error接口的是*errorString指针,而不是原始的errorString,为了让每次New分配的error实例都不相等。避免重大错误与小错误相等
fmt.Println(errors.New("EOF") == errors.New("EOF")) // false
errors.New不常见。更常见的是fmt.Errorf,它额外提供了字符串格式化功能。
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}
8.表达式求值器
将创建一个求值器用来计算算数表达式。
使用一个接口Expr,来代表这种语言中的任意一个表达式(暂时无方法)。
type Expr interface{}
定义五种类型,代表特定类型的表达式:
// Var表示一个变量
type Var string
// literal表示一个数字常量
type literal float64
//unary 表示一元操作符表达式:-y
type unary struct {
op rune
x Expr
}
//binary表示二元操作符表达式: x+y
type binary struct {
op rune
x, y float64
}
// call 表示函数调用表达式, sin(x)
type call struct {
fn string
args []Expr
}
要包含变量的表达式进行求值,需要一个数组映射:
type Env map[Var]float64
因为每种类型的表达式都需要一个Eval方法来返回表达式在一个给定上下文下的值。可加入到接口中:
type Expr interface{
Eval(env Env) float64
}
分别实现Eval方法:
// Var中查询,有则返回值本身,无则返回0
func (v Var) Eval (env Env) float64 {
return env[v]
}
// 返回的是数字常量
func (l literal) Eval (_ Env) float64{
return float64(l)
}
func ( u unary) Eval(env Env) float64 {
switch u.op {
case '+':
return +u.x.Eval(env)
case '-':
return -u.Eval(env)
}
panic(fmt.Sprintf("unsupported unary operator: %q", u.op))
}
func (b binary)Eval (env Env)float64 {
switch b.op {
case '+':
return b.x.Eval(env) + b.y.Eval(env)
case '-':
return b.x.Eval(env) - b.y.Eval(env)
case '*':
return b.x.Eval(env) * b.y.Eval(env)
case '/':
return b.x.Eval(env) / b.y.Eval(env)
}
panic(fmt.Sprintf("unsupported binary operator: %q", b.op))
}
func (c call)Eval (env Env)float64 {
switch c.fn {
case "sin":
return math.Sin(c.args[0].Eval(env))
case "pow":
return math.Pow(c.args[0].Eval(env),c.args[1].Eval(env))
case "sqrt":
return math.Sqrt(c.args[0].Eval(env))
}
panic(fmt.Sprintf("unsupported function call: %s", c.fn))
}
这样其实是不完整的,因为可能遇到解析不了的会报错。为了可以更快的发现错误,可以在接口中定义Check方法,用于在表达式语法树上检查静态错误。
type Expr interface{
Eval(env Env) float64
Check(vars map[Var]bool) error
}
// 返回空即可
func (v Var) Check (vars map[Var]bool) error {
vars[v] = true
return nil
}
func (literal) Check (vars map[literal]bool) error {
return nil
}
//先判断操作符是否合法,在递归检查操作数
func (u unary) Check (vars map[Var]bool) error {
if !strings.ContainsRune("+-", u.op){
return fmt.Errorf("unexpected unary op %q", u.op)
}
return u.x.Check(vars)
}
//先判断操作符是否合法,在递归检查操作数
func (b binary) Check(vars map[Var]bool) error {
if !strings.ContainsRune("+-*/", b.op) {
return fmt.Errorf("unexpected binary op %q", b.op)
}
if err := b.x.Check(vars); err != nil {
return err
}
return b.y.Check(vars)
}
// 函数是否已知,参数个数是否正确,递归检查每个参数
func (c call) Check(vars map[Var]bool) error {
arity, ok := numParams[c.fn]
if !ok {
return fmt.Errorf("unknown function %q", c.fn)
}
if len(c.args) != arity {
return fmt.Errorf("call to %s has %d args, want %d",
c.fn, len(c.args), arity)
}
for _, arg := range c.args {
if err := arg.Check(vars); err != nil {
return err
}
}
return nil
}
var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1}
9.类型断言
类型断言是一个作用在接口上的操作,基本形式x.(T),其中x是一个接口类型的表达式,T是一个断言类型。
类型断言会检查作为操作数的动态类型是否满足断言类型。
- 断言类型是一个具体类型,则检查x的动态类型是否就是T。如果成功则取出动态值:
func main() {
var w io.Writer
w = os.Stdout
f := w.(*os.File)
c := w.(*bytes.Buffer)
fmt.Println(f) // f == os.File
fmt.Println(c) // 是*os.File, 不是*bytes.Buffer
}
// panic: interface conversion: io.Writer is *os.File, not *bytes.Buffer
- 断言类型T是一个接口类型,检查x的动态类型是否满足T。如果成功则仍然是一个接口值,只是接口的类型变为T。它只是接口类型的转换,但保留了接口值中的动态类型和动态值部分。
func main() {
var w io.Writer
w = os.Stdout
rw1 := w.(io.ReadWriter)
w = new(ByteCounter)
rw2 := w.(io.ReadWriter)
fmt.Println(rw1) // 成功
fmt.Println(rw2) // 崩溃,*main.ByteCounter is not io.ReadWriter: missing method Read
}
type ByteCounter int
func (c *ByteCounter) Write(p []byte) (int, error) {
*c += ByteCounter(len(p)) // convert int to ByteCounter
return len(p), nil
}
10.使用类型断言来识别错误
在Go中,I/O会因为很多原因失败,os包中提供了三个帮助函数,对错误进行分类,包括文件已存储,文件未找到和权限不足:
func IsExist(err error) bool {
return isExist(err)
}
func IsNotExist(err error) bool {
return isNotExist(err)
}
func IsPermission(err error) bool {
return isPermission(err)
}
还有一种方式,是用专门的类型来表示结构化的错误值。在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的结构则保留了错误所有的底层信息。用法如下:
func main() {
_, err := os.Open("/no/such/file")
fmt.Println(err)
fmt.Printf("%#v\n", err)
}
输出:
&os.PathError{Op:"open", Path:"/no/such/file", Err:0x3}
打印出了所有信息。
11.通过接口类型断言来查询特征
在网络的连接中,Write方法需要一个字节slice,而我们想写入的是一个字符串,所以[]byte(...)转换是必须的。这就涉及到了内存分配与复制,而内存分配会导致性能下降。
在net/http中,可看到Write对应的动态类型还支持一个能搞笑写入字符串的WriteString方法,这个方法避免了临时内存的分配和复制。
可以假定一个新的接口,这个接口只包含WriteString方法,然后使用类型断言来判断w的动态类型是否满足这个新接口。
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)) // 分配了临时内存
}
一个具体的类型是否满足stringWriter接口仅仅由它拥有的方法决定,而不是这个类型与一个接口类型之间的一个关系声明。对于上面的例子来说,如果一个类型满足下面的接口,那么WriteString(s)必须与Write([]byte(s)等效。
interface {
io.Writer
WriteString(string) (n int, err error)
}
12.类型分支
接口中有两类不同的风格。
- 第一种风格:接口中的各种方法突出了满足这个接口的具体类型之间的相似性,但隐藏了各个具体类型的布局和各自特有的功能,比如
fmt.Stringer和sort.Interface等。
type Stringer interface {
String() string
}
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
- 第二种风格:充分利用了接口值能够容纳各种具体类型的能力,把接口作为这个类型的联合(union)来使用。类型断言用来在运行时区分这些类型并分别处理。这种风格强调的是满足这个接口的具体类型,而不是这个接口的方法,不注重信息隐藏。这种风格的接口方式称为可识别联合。
关于第二种风格,Go语言的数据库SQL查询API允许我们干净地分离查询中不变部分和可变部分。下面展示一个示例客户端:
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。在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)
} else {
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}
也可以这么写:
switch x.(type){
case nil: // ...
case int, uint: // ...
case bool: // ... // ... // ...
case string: // ... // ...
default: // ...
}
要注意的是,分支的顺序在一个或多个分支是接口类型时会变得重要,因为可能有两个分支都能满足。其实类型分支语句也有一种扩展形式,它用来把每个分支中提取出来的原始值绑定到一个新的变量:
switch x := x.(type) {/*....*/}
所以可以改写:
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))
}
}
每个单一类型的分支块内,变量x的类型都与该分支的类型一致。