Stack—Custom Types with Methods
Go虽然支持面向对像的编程,但它并没有提供类,也没有提供继承(is-a relationships). Go支持的是创建自定义类型,然后使用聚合(aggregation has-a relationships),实现Java中的继承。Go也支持将数据从行为中完全分离,同时支持鸭子类型(duck typing). Duck typing是一个强大的抽像机制,意思是一个值可以被处理(e.g., passed to functions), 依赖于值的实际类型所提供的方法。 这个专业术语源自于, “如果它走起来像鸭子,它的叫声像鸭子,则它就是一只鸭子”。 这相对于类和继承来说更加灵活和强大。
Go的数据表示,使用的是基本的内置类型关键字,比如 struct, bool, int, string. 或者使用structs聚合类型。 Go的自定义类型是基于基本类型之睥, or on structs, or 其它自定义类型。
Go也支持命名与匿名的自定义类型,匿名类型对于有相同的结构可以自由交换,但是不可以任何的方法。 命名的自定义类型可以有方法。并且这此方法组成了此类型的接口。 命名的自定义类型,即使它们有相同的结构,也不能用于交换。(除非另有说明,本书引用的自定义类型都是命名类型)
一个接口是一个类型,形式上讲,由特定的方法组成。 一个接口是抽像的,不可以被实例化。 一个具体类型(i.e., noninterface)实现了指定接口的方法。 则此具体类型的值可以用于此接口类型的值。 具体类型和接口在形式上没有直接的联系,只是在具体类型上实现了接口所要求的方法。
空接口interface{}由于没有要求(因为它没有要求任何方法). 它可以代表任何的值(等效于一个指针可以代表任何值),而不管这个值是内置的还是自定义类型。顺便说明一下,在Go的术语中,类型和值,就相当于其它语言中的类和对像或者实例.
函数或者方法的参数可以是任意的内置或者自定义类型——或者是interface type.
此程序在stacker/stacker.go, 以下是程序的导入包
import (
"fmt"
"stacker/stack"
)
fmt package是Go标准库中的包,而stack 包是一个相对于stacker应用程序的本地包。 一个Go程序包的加载首先是搜索在GOPATH的路径,然后在搜索GOROOT. 在实现的案例中, 程序的源文件位于$HOME/goeg/src/stacker/stacker and stack package在$HOME/goeg/src/stacker/stack/stack.go. 只要GOPATH是$HOME/goeg, GO就可以编译这两个文件。
导入路径使用Unix-style "/", 即例在Windows下也是用这种方式。 每一个本地包应该保存到跟包相同名字的目录中。本地包也可以有它们自己的包(e.g., like path/filepath).
以下是此程序的main()函数
func main() {
var haystack stack.Stack
haystack.Push("hay")
haystack.Push(-15)
haystack.Push([]string{"pin", "clip", "needle"})
haystack.Push(81.52)
for {
item, err := haystack.Pop()
if err != nil {
break
}
fmt.Println(item)
}
}
函数开始处声明了一个stack.Stack类型的haystack变量。在Go中引用一个包中的类型,函数,变量以及其它的元素,都是通过pkg.item的语法。pkg是package name的最后一个部分。 然后我们在将多个元素添加到stack中,之在循环中弹出元素,并且输出,直接stack中没有元素后终止无限循环。
令人惊奇的是,我们自定义的stack可以存储不同类型的元素。这是因为stack.Stack类型只是简单的存储了interface{}元素(i.e., values of any type),而不用去管它们的实现类型是什么 。当然stack中的元素被使用时,则需要关注元素的实际类型是什么。 但在这里,我们只是使用到fmt.Println()函数和Go的内省机制(from the reflect package)来发现元素的实际类型,并打印。(Reflection 将在第9.4.9中讲到)
另一个Go的特点是for loop不需要条件,它是一个无限循环。所要在很多情况下使用break或者return语句来终止循环。
Go函数和方法可以返回单个或者多个值。在Go中,经常会在返回值的最后部分,返回一个错误值(of type error), 以报告错误。
既然我们已经看了stack.Stack类型的使用,下面我们看看它的实现(stacker/stack/stack.go)
package stack
import "errors"
type Stack []interface{}
此文件的开始按照惯例指定了包的名字。 然后在导入需要的包——这里仅需要errors.
当我们在Go中定义一下命名的自定义类型时,我们所做的是为新的类型绑定一个标识符。这个新的类型跟已存在的类型(内置或者自定义类型)的底层表示(已有的类型构成新类型的底层,现有类型是在底层类型上进行包装)——Go会对新类型与底层类型区别对待。 在这里,Stack类型可以说是一个包含interface{}类型值的slice的新名字——可以认为它跟纯[]interface{}不同.
由于所有的Go类型都满足于空接口, 所以Stack中可以保存任意类型的值。
内置的集合类型(maps and slices), commuication channels(缓冲区buffer) and strings. 都可以使用len()函数返回它们的长度(or buffer size)。 相似的, slices and channel也可以通过cap()函数返回它们的容量。
由于Stack类型是使用一个slice作为它的底层表达. 所以我们也应该为它提供Stack.Len() 和 Stack.Cap()方法,以便它可以返回底层的slice长度和容量
func (stack Stack) Len() int {
return len(stack)
}
两个函数的定义都使用了func关键词。在func和方法名之间指名的是方法可以应用于哪种类型的值。
在很多情况下,一个变量值是它调用此方法的值(调用此方法的对像) —— 在这里我们使用stack变量(跟package的名字没有冲突). 在Go的专业术语上讲,这个调用方法的值可以称为receiver.
在这个例子中,receiver类型为Stack. 所以这个receiver是通过值传递的。 这意味着所有对这个receiver的修改都不会影响到原来的值。这对于不需要修改receiver不是什么问题,比如Stack.Len()方法,仅是返回slice的长度。
Stack.Cap()方法跟Stack.Len()方法一样。唯一不同的时Stack.Cap()方法返回的receiver stack的cap() 而不是len(). 在源代码中还有一个Stack.IsEmpty()方法,它跟Stack.Len()类似,只是返回的一个布尔值,表明stack len()是否为0.
func (stack *Stack) Push(x interface{}) {
*stack = append(*stack, x)
}
Stack.Push()方法可以被指向到Stack的指针调用。它的参数是interface{}, 所以可以传递任意类型。内置 的append()函数接受一个slice和一个或者多个要被添加的值,并返回一个(possibly new)slice.
由于Stack.Push()方法一直工作(除非运行时常出了内存),所以我们不需要返回一个error值,以表明成功或者失败。
如果我们需要修改一个值,我们必须使得它的receiver为一个指针。 一个指针是一个变量,它保存的是另一个值的内存地址。 使用指针的一个原因是它更加高效——比如,我们有一个大的类型值,指针传递相对于传递值本身,则更廉价。另一个原因是我们可以修改值。比如,当一个变量传递给函数, 这个函数只是获得了这个值的副本(e.g., the stack passed to the stack.Len() 函数). 这意味着在函数内任何对变量的修改都不会影响到原始值。 如果我们需要修改原始值——在这里我们是要向stack中添加值——我们必须传递一个指向原始值的指针,然后就可以在函数内部修改。
指针的声明是在类型名称的前面加上一个 *. 在Stack.Push()方法中, stack变量是类型 *Stack. 也就是说stack变量保存的是指向Stack值的指针,而不是实际的Stack值。然后我们可以通过*stack,取得一指针的实际值。
在Go中,使用make()函数可以创建channels, maps, slice. make()返回的是指向到它创建的值的引用。引用跟指针很类似。但不同的是引用不需要取值,所以我们不需要使用到星号。如果我们需要在一个函数或者方法中调用append()(函数修改一个slice, 则我们必须传递这个slice的指针,而不是引用类型。 这是因为append()有时返回的不同的slice引用,而不是传递过来的引用。
func (stack Stack) Top() (interface{}, error) {
if len(stack) == 0 {
return nil, errors.New("can't Top() an empty stack")
}
return stack[len(stack)-1], nil
}
Stack.Top()方法返回stack中最顶层的元素和一个 nil error value; 或者是一个nil元素和一个non-nil error value, 如果stack为空的话。
error类型是一个interface type. 它仅指定了一个方法,方法签名为Error() string. 一般来说,Go的库函数中会在返回值的最后加上一个error,以表明成功(where error is nil)或者failture.
Go使用nil表示零指针(and for zero references); 表示一个指针没有指向任何事物或者没有引用任何事物。这一类指针只可以用于条件和赋值语句中。
Go从不隐式的调用构造器(构造函数),而是在一个值被创建时初始化为零值。 比如,数字被初始为0。 字符串为空字符串,指针为nil, structs中的字段也是相似的初始化。如果零值不适合我们,我们也可以写一个构造函数,然后在明确的调用它———如我们创建的new error.
func (stack *Stack) Pop() (interface{}, error) {
theStack := *stack
if len(theStack) == 0 {
return nil, errors.New("can't Pop() an empty stack")
}
x := theStack[len(theStack)-1]
*stack = theStack[:len(theStack)-1]
return x, nil
}
Stack.Pop()方法被用于删除并返回最上面的元素。跟Stack.Top()方法一样,它返回item和一个nil error, 或者如果stack为空,则返回一个nil item and a non-nil error.
由于这个方法会删除返回的元素,这个方法的接收者必须是一个指针。为了便于访问指针的实际值,我们把实际的stack赋值给本地变量(theStack).
代码
// main.go
package main
import (
"fmt"
"stacker/stack"
"strings"
)
func main() {
var haystack stack.Stack
haystack.Push("hay")
haystack.Push(-15)
haystack.Push([]string{"pin", "clip", "needle"})
haystack.Push(81.52)
for {
item, err := haystack.Pop()
if err != nil {
break
}
fmt.Println(item)
}
var aStack stack.Stack
aStack.Push("Aarvark")
aStack.Push(5)
aStack.Push(19)
x, err := aStack.Top()
fmt.Println(x)
aStack.Push(-6e-4)
aStack.Push("Baker")
aStack.Push(-3)
aStack.Push("Cake")
aStack.Push("Dancer")
x, err = aStack.Top()
fmt.Println(x)
aStack.Push(11.7)
fmt.Println("stack is empty", aStack.IsEmpty())
fmt.Printf("Len() == %d Cap == %d\n", aStack.Len(), aStack.Cap())
difference := aStack.Cap() - aStack.Len()
for i := 0; i < difference; i++ {
aStack.Push(strings.Repeat("*", difference-i))
}
fmt.Printf("Len() == %d Cap == %d\n", aStack.Len(), aStack.Cap())
for aStack.Len() > 0 {
x, _ = aStack.Pop()
fmt.Printf("%T %v\n", x, x)
}
fmt.Println("stack is empty", aStack.IsEmpty())
x, err = aStack.Pop()
fmt.Println(x, err)
x, err = aStack.Top()
fmt.Println(x, err)
}
//stacker/stack/stack.go
package stack
import "errors"
type Stack []interface{}
func (stack *Stack) Pop() (interface{}, error) {
theStack := *stack
if len(theStack) == 0 {
return nil, errors.New("can't Pop() an empty stack")
}
x := theStack[len(theStack)-1]
*stack = theStack[:len(theStack)-1]
return x, nil
}
func (stack *Stack) Push(x interface{}) {
*stack = append(*stack, x)
}
func (stack Stack) Top() (interface{}, error) {
if len(stack) == 0 {
return nil, errors.New("can't Top() an empty stack")
}
return stack[len(stack)-1], nil
}
func (stack Stack) Cap() int {
return cap(stack)
}
func (stack Stack) Len() int {
return len(stack)
}
func (stack Stack) IsEmpty() bool {
return len(stack) == 0
}
//stacker/stack/stack_test.go
package stack_test
import (
"stacker/stack"
"testing"
)
func TestStack(t *testing.T) {o
count := 1
var aStack stack.Stack
assertTrue(t, aStack.Len() == 0, "expected empty Stack", count) // 1
count++
assertTrue(t, aStack.Cap() == 0, "expected empty Stack", count) // 2
count++
assertTrue(t, aStack.IsEmpty(), "expected empty Stack", count) // 3
count++
value, err := aStack.Pop()
assertTrue(t, value == nil, "expected nil value", count) // 4
count++
assertTrue(t, err != nil, "expected error", count) // 5
count++
value1, err := aStack.Top()
assertTrue(t, value1 == nil, "expected nil value", count) // 6
count++
assertTrue(t, err != nil, "expected error", count) // 7
count++
aStack.Push(1)
aStack.Push(2)
aStack.Push("three")
assertTrue(t, aStack.Len() == 3, "expected nonempty Stack", count) // 8
count++
assertTrue(t, aStack.IsEmpty() == false, "expected nonempty Stack",
count) // 9
count++
value2, err := aStack.Pop()
assertEqualString(t, value2.(string), "three", "unexpected text",
count) // 10
count++
assertTrue(t, err == nil, "no error expected", count) // 11
count++
value3, err := aStack.Top()
assertTrue(t, value3 == 2, "unexpected number", count) // 12
count++
assertTrue(t, err == nil, "no error expected", count) // 13
count
aStack.Pop()
assertTrue(t, aStack.Len() == 1, "expected nonempty Stack", count) //14
count++
assertTrue(t, aStack.IsEmpty() == false, "expected nonempty Stack",
count) // 15
count++
value4, err := aStack.Pop()
assertTrue(t, value4 == 1, "unexpected number", count) // 16
count++
assertTrue(t, err == nil, "no error expected", count) // 17
count++
assertTrue(t, aStack.Len() == 0, "expected empty Stack", count) // 18
count++
assertTrue(t, aStack.IsEmpty(), "expected empty Stack", count) // 19
count++
}
// assertTrue() calls testing.T.Error() with the given message if the
// condition is false.
func assertTrue(t *testing.T, condition bool, message string, id int) {
if !condition {
t.Errorf("#%d: %s", id, message)
}
}
// assertEqualString() calls testing.T.Error() with the given message if
// the given strings are not equal.
func assertEqualString(t *testing.T, a, b string, message string, id int) {
if a != b {
t.Errorf("#%d: %s \"%s\" !=\n\"%s\"", id, message, a, b)
}
}