方法
指针 VS. 值
可以为任何命名类型(除了指针或者接口外)定义方法。方法的接收者不是一定要是结构的。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21type ByteSlice []byte
// 值方法
func (slice ByteSlice) Append(data []byte) []byte { // 要求该方法返回更新的值
.....
}
// 指针方法
func (p *ByteSlice) Append(data []byte) { // 这个方法可以修改调用者,因此不用返回
slice := *p
....
*p = slice
}
func (p *ByteSlice) Write(data []byte) (n int, err error) {
slice := *p
// Again as above.
*p = slice
return len(data), nil
}
// 定义了Write方法后,类型*ByteSlice就满足了标准接口io.Write的要求。
var b ByteSlice
fmt.Fprintf(&b, "This hour has %d days\n", 7)
// 这里传递ByteSlice的地址是因为,只有*ByteSlice满足了标准接口io.Write的要求。
这是因为,指针方法可以修改接收者,而对值引用指针方法可能会造成该方法接收到该值的一份拷贝,从而使得任何的修改都会被丢弃。因此,Go不允许这种错误。当该值是可寻址的时候,Go会自动插入取址运算符(&),来处理在值上引用指针方法的这种常见场景。例如,在上面的例子中,变量b
是可寻址的,因此,通过b.Write
就可以调用它的Write
方法。编译器会为我们重写成(&b).Write
。
接口和其他类型
接口
Go中的接口提供了一种指定对象行为的方式:如果某个东东可以做这个事,那么,它就可以被用在这里。在Go中,只有一两个方法的接口比比皆是,并且常常会根据方法来赐名,例如,io.Writer
就是那些实现了Write
的东东。
一个类型可以实现多个接口。例如,一个集合可以被包sort
中的程序排序,只要它实现了sort.Interface
(包括Len()
/ Less(i, j int) bool
/ Swap(i, j int)
),另外,它还可以有一个自定义的格式器(String
方法)。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25type Sequence []int
// sort.Interface所要求的方法
func (s Sequence) Len() int {
return len(s)
}
func (s Sequence) Less(i, j int) bool {
return s[i] < s[j]
}
func (s Sequence) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// 用以打印的方法:在打印前先排序
func (s Sequence) String() string {
sort.Sort(s)
str := "["
for i, elem := range s {
if i > 0 {
str += " "
}
str += fmt.Sprint(elem)
}
return str + "]"
}Sequence
的String
方法重新实现了Sprint
已经为切片实现的东西,因此,我们可以在调用Sprint
之前将Sequence
转换成一个普通的[]int
,从而来利用它已实现的东东: 1
2
3
4func (s Sequence) String() string {
sort.Sort(s)
return fmt.Sprint([]int(s))
}
在Go程序中,会转换一个表达式的类型,从而访问一个不同的方法集。还是上面的这个例子,我们可以使用现有的类型sort.IntSlice
来把上面的代码缩减成: 1
2
3
4
5
6
7type Sequence []int
// 用以打印的方法:在打印前先排序
func (s Sequence) String() string {
sort.IntSlice(s).Sort()
return fmt.Sprint([]int(s))
}Sequence
/ sort.IntSlice
/ []int
)的能力,这些类型每一种都完成部分的工作。
接口转换和类型断言(type assertion)
类型选择(type switch)是这样一个东东的:接收一个接口,然后对于switch
中的每一个case
,在某种意义上将其转换成那个case
的类型。下面是fmt.Printf
中的代码如何使用类型选择来将一个值转换成一个字符串的简化示例: 1
2
3
4
5
6
7
8
9
10
11type Stringer interface {
String() string
}
var value interface{} // 调用者提供的值
switch str := value.(type) {
case string:
return str
case Stringer:
return str.String()
}newVal, ok := value.(typeName)
* 其中,newVal
是一个具有静态类型typeName
的新值 * typeName
必须是该接口value
具有的具体类型,或者是value
可以转换的第二个接口类型。 * ok
是个可选项。如果不带,那么当值不是/无法转换成typeName
的时候,程序会崩溃并抛出运行时错误。因此,可以使用ok
(“comma, ok”习语)来安全地测试该值是否为typeName
1
2
3
4
5
6
7str, ok := value.(string)
if ok {
fmt.Printf("string value is: %q\n", str)
} else {
fmt.Printf("value is not a string\n")
}
// 如果类型断言失败,那么,str将仍然存在,并且类型为string,但是它将是一个零值,即""