好好学习,天天向上

Go, go|小白的go小抄:方法、接口和其他类型

方法

指针 VS. 值

可以为任何命名类型(除了指针或者接口外)定义方法。方法的接收者不是一定要是结构的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type 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的要求。
对于接收者而言,关于指针vs.值的规则是:可以对指针和值引用值方法,但是,只能对指针引用指针方法。

这是因为,指针方法可以修改接收者,而对值引用指针方法可能会造成该方法接收到该值的一份拷贝,从而使得任何的修改都会被丢弃。因此,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
25
type 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 + "]"
}
#### 转换 在上面的例子中,SequenceString方法重新实现了Sprint已经为切片实现的东西,因此,我们可以在调用Sprint之前将Sequence转换成一个普通的[]int,从而来利用它已实现的东东:
1
2
3
4
func (s Sequence) String() string {
sort.Sort(s)
return fmt.Sprint([]int(s))
}
这里的转换并不会创造一个新的值,它只是暂时假装现有的值有一个新的类型。(有其他合法的转换,例如从整型到浮点数,这确实会创造一个新的值。)

在Go程序中,会转换一个表达式的类型,从而访问一个不同的方法集。还是上面的这个例子,我们可以使用现有的类型sort.IntSlice来把上面的代码缩减成:

1
2
3
4
5
6
7
type Sequence []int

// 用以打印的方法:在打印前先排序
func (s Sequence) String() string {
sort.IntSlice(s).Sort()
return fmt.Sprint([]int(s))
}
这样,Sequence就不需要实现多个接口(排序和打印)了。使用一个数据项能转换成多种类型(Sequence / sort.IntSlice / []int)的能力,这些类型每一种都完成部分的工作。

接口转换和类型断言(type assertion)

类型选择(type switch)是这样一个东东的:接收一个接口,然后对于switch中的每一个case,在某种意义上将其转换成那个case的类型。下面是fmt.Printf中的代码如何使用类型选择来将一个值转换成一个字符串的简化示例:

1
2
3
4
5
6
7
8
9
10
11
type Stringer interface {
String() string
}

var value interface{} // 调用者提供的值
switch str := value.(type) {
case string:
return str
case Stringer:
return str.String()
}
类型断言(type assertion):接收一个接口值,然后从中提取所指定的显式类型的值。 * 语法为newVal, ok := value.(typeName) * 其中,newVal是一个具有静态类型typeName的新值 * typeName必须是该接口value具有的具体类型,或者是value可以转换的第二个接口类型。 * ok是个可选项。如果不带,那么当值不是/无法转换成typeName的时候,程序会崩溃并抛出运行时错误。因此,可以使用ok(“comma, ok”习语)来安全地测试该值是否为typeName
1
2
3
4
5
6
7
str, ok := value.(string)
if ok {
fmt.Printf("string value is: %q\n", str)
} else {
fmt.Printf("value is not a string\n")
}
// 如果类型断言失败,那么,str将仍然存在,并且类型为string,但是它将是一个零值,即""
### 参考 - Effective Go

请言小午吃个甜筒~~