会引起程序的崩溃,因此panic一般用于严重错误,如程序内部的逻辑不一致。
Recover捕获异常
如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recoverrecover会返回nil。
func Parse(input string) (s *Syntax, err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("internal error: %v"
, p)
}
}()
// ...parser...
}
deferred函数帮助Parse从panic中恢复。在deferred函数内部,panic value被附加到错误信息中;并用err变量接收错误信息,返回给调用者。我们也可以通过调用runtime.Stack往错误信息中添加完整的堆栈调用信息。
方法
GO不支持类,但支持方法,可以为结构体或其他类型定义方法,方法就是一类带特殊的 接收者 参数的函数。方法接收者在它自己的参数列表内,位于 func
关键字和方法名之间。
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 { //定义结构体方法
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs()) //v.Abs() 调用方法
}
其形式类似于将函数声明中的形参放到函数名之前
只能为同一个包的类型接收者声明方法,不能为其他包内定义的类型声明方法
可以为其他类型定义方法,但不能为内置类型(如:int
)定义方法
type MyFloat float64
func (f MyFloat) Abs() float64 { //ture
if f < 0 {
return float64(-f)
}
return float64(f)
}
//cannot define new methods on non-local type float64
//func (f float64) Abs() float64 {
// if f < 0 {
// return float64(-f)
// }
// return float64(f)
//}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
指针接收者
使用指针接收者可以改变接收者自身的值
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 main() {
v := Vertex{3, 4}
v.Scale(10) //v = {30,40}
fmt.Println(v.Abs())
}
调用函数时,指针类型的形参必须接受一个指针;调用方法时接收者为变量时可以是指针也可以为值,编译器会自动解引用或取地址。
var v Vertex
ScaleFunc(v, 5) // 编译错误!
ScaleFunc(&v, 5) // OK
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
在现实的程序里,一般会约定如果Point这个类有一个指针作为接收器的方法,那么所有Point 的方法都必须有一个指针接收器,即使是那些并不需要这个指针接收器的函数。
只有类型(Point)和指向他们的指针(*Point),才是可能会出现在接收器声明里的两种接收器。 此外,为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的,比如下面这个例子:
type P *int
func (P) f() { /* ... */ } // compile error: invalid receiver type
- 不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型 进行调用的,编译器会帮你做类型转换。
- 在声明一个method的receiver该是指针还是非指针类型时,你需要考虑两方面的内部,第 一方面是这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝;第二方面是如果你用指针类型作为receiver,那么你一定要注意,这种指针类型指向 的始终是一块内存地址,就算你对其进行了拷贝。熟悉C或者C艹的人这里应该很快能明 白
使用嵌入类型扩展类型
使用嵌入结构体时,被嵌入结构体可以直接调用嵌入结构体的方法,
import "image/color"
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5" Distance是point类型的方法,p的类型为ColoredPoint,但可以直接调用Distance
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10" 但参数类型为Point时,必须显示调用point字段
方法值和方法表达式
可以将特定变量的方法调用赋值给变量,通过变量调用方法,其形式类似于函数变量的赋值:
p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance // method value,选择器返回一个方法值
fmt.Println(distanceFromP(q)) // "5"
scaleP := p.ScaleBy // method value,选择器返回一个方法值
scaleP(2) // p becomes (2, 4)
p.distance
,p.ScaleBy称为
选择器,选择器返回一个方法值。
在一个包的API需要一个函数值、且调用方希望操作的是某一个绑定了对象的方法的话,方
法"值"会非常实用。举例来说,下面例子中的time.AfterFunc
这个函数的功能是在指定的延迟时间之后来执行一个(译注:另外的)函数。且这个函数操作的是一个Rocket对象r
type Rocket struct { /* ... */ }
func (r Rocket) Launch() { / ... */ }
r := new(Rocket)