[开发语言 | Go] 02 - 方法和接口

74 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

本节简要介绍 Go 语言中的方法与接口,虽然与其它面向对象对象的语言有所差异,但基本上可以找到类似对应的概念。

  1. Go没有类,但是可以为类型定义方法。
  2. 方法是拥有接收者参数的函数。
  3. 可以为非结构体类型声明方法。但是方法的接收者必须是在同一个包内定义的类型。
  4. 使用指针接收者的方法可以修改接收者指向的值,使用值接收者的方法只能对原值的拷贝进行操作。所以使用指针接受者的做法更常见。
  5. 使用指针参数的函数只能接收对应类型的指针,而使用值参数的函数只能接收对应类型的值;相比之下,使用指针/值接收者的方法在调用时,将同时允许对应类型的值或指针为接收者。也就是说,Go可以根据方法的接收者类型将v.Method()解释为(&v).Method(),或者将p.Method()解释为(*p).Method()
  6. 使用指针接收者的理由:可以修改接收者指向的值;避免值的复制。通常来讲,给定类型的方法不要混用这两种做法。
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).Scale
	fmt.Println(v.Abs())
}
  1. 一个接口类型定义为一组方法签名的集合。一个接口值可以保存任何实现了该接口的类型的值。
  2. 一个类型通过实现一个接口的所有方法来隐式实现该接口,此外并没有显式的声明或者类似"implements"这样的关键字。
  3. 一个接口值保存了一个具体类型的值(value, type),调用接口值的方法会执行具体类型的同名函数。
type I interface {
	M()
}

// 类型 *T 隐式实现了接口 I。注意,不是类型 T
type T struct {
	S string
}
func (t *T) M() {
	fmt.Println(t.S)
}

// 类型 F 隐式实现了接口 I
type F float64
func (f F) M() {
	fmt.Println(f)
}

func main() {
	var i I

	i = &T{"Hello"}
	i.M()

	i = F(math.Pi)
	i.M()
}
  1. 如果一个接口的具体值为 nil,调用其方法将使用一个 nil 的接收者。因此方法中需要处理接收者为 nil 的情况。注意,具体值为 nil 的接口值本身不为 nil。

  2. 一个 nil 的接口值不保存值或者类型var i I,调用其方法会产生运行时错误。

  3. 未指定任何方法的接口类型是空接口。空接口可以保存任何类型的值,它通常被用来处理未知类型的值。

  4. 类型断言提供了访问接口具体值的方法。语句t := i.(T)断言接口值i保存的具体类型为T,并将该T类型的值赋给变量t

    a. 如果i并未保存T类型的值,这将触发panic;

    b. 类型断言可以返回两个值t, ok := i.(T),如果断言正确,oktruet保存具体值;如果断言错误,okfalsetT类型的零值。

  5. 类型switch与一般的switch语句类似,不过比较的case是类型而非值。

    a. 在每一个case内,变量v将以对应的类型保存i的具体值。

    b. 在defaultcase中,变量v的类型与i一致。

switch v := i.(type) {
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}
  1. Stringerfmt包中定义的一个常见接口,一个Stringer可以用一个字符串描述自身。接口包含一个String() string方法。
  2. error也是一个内建的接口,包含一个Error() string方法。
type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}
	z := 1.0
	for i := 1; i < 10; i++ {
		z -= (z*z - x) / (2*z)
	}
	return z, nil
}

func main() {
	x, err := Sqrt(-2)
	if err == nil {
		fmt.Println(x)
	}
	fmt.Println(err)
}
  1. io.Readerio包中定义的接口,表示数据流的读取端。接口包含一个func (T) Read(b []byte) (n int, err error)方法。