首页 > 代码库 > Go 方法和接口
Go 方法和接口
方法
Go 没有类。然而,仍然可以在结构体类型上定义方法。
方法接收者 出现在 func
关键字和方法名之间的参数中。
package mainimport ( "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 main() { v := &Vertex{3, 4} fmt.Println(v.Abs())}
方法(续)
事实上,可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。
不能对来自其他包的类型或基础类型定义方法。
package mainimport ( "fmt" "math")type MyFloat float64func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f)}func main() { f := MyFloat(-math.Sqrt2) fmt.Println(f.Abs())}
接收者为指针的方法
方法可以与命名类型或命名类型的指针关联。
刚刚看到的两个 Abs
方法。一个是在 *Vertex
指针类型上,而另一个在 MyFloat
值类型上。 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。
尝试修改 Abs
的定义,同时 Scale
方法使用 Vertex
代替 *Vertex
作为接收者。
当 v
是 Vertex
的时候 Scale
方法没有任何作用。`Scale` 修改 `v`。当 v
是一个值(非指针),方法看到的是 Vertex
的副本,并且无法修改原始值。
Abs
的工作方式是一样的。只不过,仅仅读取 `v`。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。
package mainimport ( "fmt" "math")type Vertex struct { X, Y float64}func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f}func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y)}func main() { v := &Vertex{3, 4} v.Scale(5) fmt.Println(v, v.Abs())}
接口
接口类型是由一组方法定义的集合。
接口类型的值可以存放实现这些方法的任何值。
注意: 左边的代码会导致编译失败。
由于 Abs
只定义在 *Vertex
上,而不是 `Vertex`。 所以 Vertex
不满足 `Abser`。
package mainimport ( "fmt" "math")type Abser interface { Abs() float64}func main() { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} a = f // a MyFloat 实现了 Abser a = &v // a *Vertex 实现了 Abser // 下面一行,v 是一个 Vertex(而不是 *Vertex) // 所以没有实现 Abser。 a = v fmt.Println(a.Abs())}type MyFloat float64func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f)}type Vertex struct { X, Y float64}func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y)}
隐式接口
类型通过实现那些方法来实现接口。
没有显式声明的必要。
隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
包 io 定义了 Reader
和 `Writer`;其实不一定要这么做。
package mainimport ( "fmt" "os")type Reader interface { Read(b []byte) (n int, err error)}type Writer interface { Write(b []byte) (n int, err error)}type ReadWriter interface { Reader Writer}func main() { var w Writer // os.Stdout 实现了 Writer w = os.Stdout fmt.Fprintf(w, "hello, writer\n")}
错误
错误是可以用字符串描述自己的任何东西。主要思路是由预定义的内建接口类型 `error`,和方法 `Error`,返回字符串:
type error interface { Error() string}
当用 fmt
包的多种不同的打印函数输出一个 error
时,会自动的调用该方法。
package mainimport ( "fmt" "time")type MyError struct { When time.Time What string}func (e *MyError) Error() string { return fmt.Sprintf("at %v, %s", e.When, e.What)}func run() error { return &MyError{ time.Now(), "it didn‘t work", }}func main() { if err := run(); err != nil { fmt.Println(err) }}