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)
    }
}