一、方法
Go没有类,但可以为类型定义方法。
方法就是一类带特殊的 接收者 参数的函数。
方法接收者在参数列表内,位于 func 关键字和方法名之间。
type Vertex struct {
X, Y float64
}// Abs方法拥有一个 名为v,类型为Vertex 的接收者
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
!!方法只是个带接收者参数的函数!!
1. 非结构体类型声明方法
package main
import (
"fmt"
"math"
)
//带Abs方法的数值类型MyFloat
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
只能为在同一包中定义的接收者类型声明方法,而不能为其他别的包中定义的类型(包括int之类的内置类型)声明方法。即接收者的类型定义和方法声明必须在同一包中。
2. 指针类型的接收者
- 对于某类型T,接收者的类型可以用*T的文法(T本身不可以是指针)。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
//为*Vertex定义了Scale方法
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
//Scale方法的接收者是指针类型(*Vertex),可以直接用值类型的变量(Vertex)来调用这个方法,
//因为Go会自动将值类型转换为指针类型,以便调用该方法。这种自动转换称为“自动取地址”
v.Scale(10)
fmt.Println(v.Abs())
}
2. 指针接收者的方法可以修改接收者指向的值(如上Scale所示)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。若使用值接收者,那么方法会对原始Vertex值的副本进行操作。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v Vertex) ScaleOri(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.ScaleOri(10) //结果5, 因为v.X,v.Y值不变
fmt.Println(v.Abs())
v.Scale(10) //结果50,v.X,v.Y值放大10倍
fmt.Println(v.Abs())
}
3. 将Scale方法重写为函数,比较这两段代码:
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(2)
ScaleFunc(&v, 10)
p := &Vertex{4, 3}
p.Scale(3)
ScaleFunc(p, 8)
fmt.Println(v, p)
}
/*
带指针参数的函数必须接受一个指针
var v Vertex
ScaleFunc(v, 5) // 编译错误!
ScaleFunc(&v, 5) // Ok
接收者为指针的方法被调用时,接收者既能是值又能是指针:
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
即Go会将v.Scale解释为(&v).Scale
*/
反之一样
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
fmt.Println(AbsFunc(v))
p := &Vertex{4, 3}
fmt.Println(p.Abs())
fmt.Println(AbsFunc(*p))
}
/* 反之一样
接受一个值作为参数的函数必须接受一个指定类型的值:
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // 编译错误!
而以值作为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
此时p,Abs()会被解释为(*P).Abs()
- 使用指针接收者的原因有二:
- 方法能够修改其接收者只想的值
- 这样可以避免在每次调用方法时复制该值,若值的类型为大型结构体时,会更高效。
通常来说,所有给定类型的方法都应该有值或指针接收者,但不应该二者混用。
二、接口
接口类型的定义为一组方法签名。
接口类型的变量可以持有任何实现了这些方法的值。
package main
import (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
type MyFloat float64
//MyFloat实现了Abser接口
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
//*Vertex实现了Abs接口
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat 实现了 Abser
a = &v // a *Vertex 实现了 Abser
fmt.Println(a.Abs())
}
1. 隐式实现
类型通过实现一个接口的所有方法来实现该接口。既然无需专门显示什么声明,也就没有“implements”关键字。隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。因此,也就无需在每一个实现上新增新的接口名称,这样同时也鼓励了明确的接口定义。
type I interface {
M()
}
type T struct {
S string
}
// 此方法表示类型 T 实现了接口 I,不过我们并不需要显式声明这一点。
func (t T) M() {
fmt.Println(t.S)
}
func main() {
var i I = T{"hello"}
i.M()
}
2. 接口值
接口也是值,它们可以像其他值一样传递。
接口值可以用作函数的参数或返回值。
在内部,接口值可以看作包含值和具体类型的元组:
(value, type)
接口值保存了一个具体类型的具体值。
接口值调用方法时会执行其底层类型的同名方法。
1. 底层值为nil的接口值
即便接口内的具体值为nil,方法仍然会被nil接收者调用。在一些语言中,这会出发一个空指针异常,但在Go中通常会写一些方法来处理。
!!保存了nil具体值的接口其本身并不为nil !!(还有类型)
package main
import (
"fmt"
)
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
func main() {
var i I
var t *T
i = t
describe(i) //(<nil>, *main.T)
i.M() //<nil>
i = &T{"Hello"}
describe(i) //(&{Hello}, *main.T)
i.M() //Hello
}
2. nil接口值
nil接口值既不保存值也不保存具备类型。为nil接口调用方法会产生运行错误,因为接口的元组内并未包含能够指明该调用哪个具体方法的类型。
package main
import "fmt"
type I interface {
M()
}
func main() {
var i I
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i) //panic: runtime error: invalid memory address or nil pointer dereference
}
3. 空接口
指定了零个方法的接口值被称为 空接口 :
interface{}
空接口可接收任何类型的值。(因为每个类型都至少实现了零个方法)
空接口被用来处理未知类型的值。例如
fmt.Print可接受类型为interface{}的任意数量的参数。
package main
import "fmt"
func main() {
var i interface{}
describe(i) //(<nil>, <nil>)
i = 42
describe(i) //(42, int)
i = "hello"
describe(i) //(hello, string)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
4. 类型断言
类型断言 提供了访问接口值底层具体值的方法。
t := i.(T)
该语句断言接口值i保存了具体类型T,并将其底层类型为T的值赋予变量t。
若i并未保存T类型的值,该语句就会触发一个panic
为了判断一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。
t, ok := i.(T)
若i保存了一个T,那么t将会是其底层值,而ok为true。
否则,ok将为false,而t将为T类型的零值,程序将不会产生panic。
package main
import "fmt"
func main() {
var i interface{} = "hello"
s := i.(string)
fmt.Println(s) //hello
s, ok := i.(string)
fmt.Println(s, ok) //hello true
f, ok := i.(float64)
fmt.Println(f, ok) //0 false
f = i.(float64)
fmt.Println(f) //panic: interface conversion: interface {} is string, not float64
}
5. 类型选择
类型选择是一种按顺序从几个类型断言中选择分支的结构。
类型选择与一般的switch语句相似,不过类型选择中的case为类型(非值),它们针对给定接口值所存储的值的类型进行比较。
switch v := i.(type) {
case T:
//v的类型为T
case S:
//v的类型为S
default:
//没有匹配, v与i 的类型相同
}
类型选择中的声明与类型断言i.(T)的语法相同,只是具体类型T被替换成了关键字type。
此选择语句判断接口值i保存的值类型是T还是S。在T或S的情况下,变量v会分贝按T或S类型保存i拥有的值。在默认(即没有匹配)的情况下,变量v与i的接口类型和值相同。
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("二倍的 %v 是 %v\n", v, v*2)
case string:
fmt.Printf("%q 长度为 %v 字节\n", v, len(v))
default:
fmt.Printf("我不知道类型 %T!\n", v)
}
}
func main() {
do(21) //二倍的 21 是 42
do("hello") //"hello" 长度为 5 字节
do(true) //我不知道类型 bool!
}