第七章-示例说明(重要)

前面是理论部分,这里是实践

####示例:表达式求值
实现简单算数表达式的求值器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package seven 

import (
"fmt"
"math"
"testing"
"strings"
)

type Expr interface {
// 根据给定的环境变量返回表达式的值
Eval(env Env) float64
Check(vars map[Var]bool) error
}
type Var string
type literal float64

type unary struct {
op rune
x, y Expr
}
type binary struct {
op rune
x, y Expr
}
type call struct {
fn string
args []Expr
}

type Env map[Var]float64

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.x.Eval(env)
}
panic(fmt.Sprintf("unsupported unary operator: %q", u.op))
}

func (b binary) Eval(env Env) float64 {
switch b.op {
case '+':
return b.y.Eval(env) + b.x.Eval(env)
case '-':
return b.y.Eval(env) - b.x.Eval(env)
case '*':
return b.y.Eval(env) * b.x.Eval(env)
case '/':
return b.y.Eval(env) / b.x.Eval(env)
}
panic(fmt.Sprintf("unsupported unary operator: %q", b.op))
}

func (c call) Eval(env Env) float64 {
switch c.fn {
case "pow":
return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env))
case "sin":
return math.Sin(c.args[0].Eval(env))
case "sqrt":
return math.Sqrt(c.args[0].Eval(env))
}
panic(fmt.Sprintf("unsupported unary operator: %s", c.fn))
}

func (v Var) Check(vars map[Var]bool) error {
vars[v] = true
return nil
}

func (literal) Check(vars map[Var]bool) error {
return nil
}
func (u unary) Check(vars map[Var]bool) error {
if !strings.ContainsRune("+-", u.op) {
return fmt.Errorf("unexpect 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("unexpect binary op %q", b.op)
}
if err := b.x.Check(vars); err != nil {
return err
}
return b.y.Check(vars)
}

var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1}

func (c call) Check(vars map[Var]bool) error {
arity, ok := numParams[c.fn]
if !ok {
return fmt.Errorf("unknow 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
}

//测试函数
func TestEval(t *testing.T) {
tests := []struct {
expr string
env Env
want string
}{
{"sqrt(A/pi)", Env{"A": 87616, "pi": math.Pi}, "167"},
{"pow(x,3)+pow(y,3)", Env{"x": 12, "y": 1}, "1729"},
{"pow(x,3)+pow(y,3)", Env{"x": 9, "y": 10}, "1729"},
{"5/9*(F-32)", Env{"F": -40}, "-40"},
{"5/9*(F-32)", Env{"F": -40}, "0"},
{"5/9*(F-32)", Env{"F": -40}, "100"},
}
var prevExpr string
for _, test := range tests {
if test.expr != prevExpr {
fmt.Print("\n%s\n", test.expr)
prevExpr = test.expr
}
expr, err := Parse(test.expr)
if err != nil {
t.Error(err)
continue
}
got := fmt.Sprintf("%.6g", expr.Eval(test.env))
fmt.Printf("\t%v => %s\n", test.env, got)
if got != test.want {
t.Errorf("%s.Eval() in %v =%q ,want %q\n", test.expr, test.env, got, test.want)

}
}
}


####类型断言
类型断言是一个使用在接口值上的操作。语法上它看起来像x.(T)被称为断言类型,这里x表示一个接口的类型和T表示一个类型,一个类型断言检查它操作对象的动态类型是否和断言的类型匹配。

1
2
3
4
5
6
7
8
9
10
var  w io.Writer
w = os.Stdout
f :=w.(*os.File) //success 检查成功
c :=w.(*bytes.Buffer) //panic 异常
cs,ok :=w.(*bytes.Buffer) //正常执行,OK返回false

//常用态
if f,ok :=w.(*os.File); ok{
//具体事项
}


####基于类型断言区别错误
引言:思考在os包中文件操作返回的错误集合。I/O可以因为任何数量的原因失败,但是有三种经常的错误必须进行不同的处理:文件已经存在(针对创建操作),找不到文件(针对读取操作),权限拒绝。os包中提供了 三个帮助函数

1
2
3
4
5
6
7
package os
func IsExits(err error) bool
func IsNotExits(err error) bool
func IsPermission(err error) bool
~~~
os包中定义了一个PathError 类型来描述在文件路径操作中涉及到的失败并且定义了一个LinkError的变量来描述设计到两个文件路径的操作,像Symlink和Rename.
PathError的实体内容:

package os
type PathError struct{
Op string
Path string
Err error
}
func (e *PathError) Error() string{
return e.Op+” “+e.Path+” “ +e.Err.Error()
}

1
大多数调用方都不知道PathError并且通过调用错误本身的Error方法来统一处理所有的错误。尽管PathError的Error方法简单地把这些字段连起来生成错误信息,一般的调用:

_,err :=os.Open(“/dir”)
fmt.Println(err)
fmt.Printf(“%#v\n”,err)
fmt.Println(os.IsNotExits(err))//true

1
2
3

* * * *
####基于类型断言询问行为

package fmt
func formatOneValue(x interface{}) string{
if err,ok:=x.(error);ok{//判断是否有错误生成
return err.Error()
}

if str,ok :=x.(Stringer);ok{//判断是否有正确信息生成。用第二个返回值OK来进行基础判断
return str.String()
}

}

1
2
3
4
* * * *
####类型开关
引言:*switch 语句可以简化ifelse type switch (类型开关) 可以简化类型断言的 if-else链*
**使用关键字面量type来简化类型判断**

switch x.(type){
case nil:
case int,uint:
case bool:
case string:
default:
}
~~~

####示例:基于标记的XML解码
应用包为:encoding/xml

####作者的建议
当设计一个新的包时,新的程序员先创建接口的集合,然后再定义符合他们的具体类型。这种方式的结果就是有很多的接口,他们中每一个仅有一个实现类型,这样是不必要的。这种接口是平白添加了一层封装而已。平台加了一层运行时消耗。你可以使用导出机制来限制一个类型的方法和一个结构体的字段是否为包外可见,接口只有当有两个或者两个以上的具体类型必须以相同的方式进行处理的时候才需要。
当一个接口只被单一的具体类型实现的时候有一种是需要封装接口的。就是由于它的依赖,这个具体的类型不能和这个接口存在一个相同的包中,这种情况下一个接口是解耦这两个包的一个很好的方式。
因为go中只有当两个或者以上的类型实现一个接口才使用接口,它们必定会从任意特定的实现细节中抽象出来。结果就是有更少和更简单方法的更小的接口。当新的类型出现时,小的接口更容易满足。对于接口设计的一个好的标准就是只考虑你需要的东西。
我们完成了对methods和接口的学习过程,go良好的支持面向对象风格的编程,但这不是说你仅仅只能使用它,不是任何事物都需要被当成一个对象;独立的函数有他们自己的用处,未封装的数据类型也是一样。一定要眼光放开,不要局限。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器