4_Methods_and_Interfaces

  • 方法

    (1) Go 没有类, 不过你可以为结构体类型定义方法, 方法就是一类__带特殊的 接收者__参数的函数。

    方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间

      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 main() {
    
          v := Vertex{3, 4}
          fmt.Println(v.Abs())
      }
    

    下面这种写法就是普通的函数

      package main
    
      import (
          "fmt"
          "math"
      )
    
      type Vertex struct {
          X, Y float64
      }
    
      func Abs(v Vertex) float64 {
          return math.Sqrt(v.X*v.X + v.Y*v.Y)
      }
    
      func main() {
          v := Vertex{3, 4}
          fmt.Println(Abs(v))
      }
    

    (2) 接收者的类型定义和方法声明必须在__同一包内__

    (3) 不能为内建类型(int, float等)声明方法

      package main
    
      import (
          "fmt"
          "math"
      )
    
      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())
      }
    

    (4) 方法 ≠ 函数,方法是一种特殊的函数

    (5) 指针接收者

    指针接收者的方法可以__修改__接收者指向的值,用值接收者就做不到(因为操作的是副本)

    示例一

      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 main() {
    
          v := Vertex{3, 4}
          v.Scale(10)
          fmt.Println(v.Abs())    //结果为50
      }
    

    示例二

      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 main() {
    
          v := Vertex{3, 4}
          v.Scale(10)
          fmt.Println(v.Abs())    //结果为5
      }
    

    (6) 对于普通的函数,也有__值传递__和__指针传递__两种

    示例一

      package main
    
      import (
          "fmt"
          "math"
      )
    
      type Vertex struct {
          X, Y float64
      }
    
      func Abs(v Vertex) float64 {
          return math.Sqrt(v.X*v.X + v.Y*v.Y)
      }
    
      func Scale(v *Vertex, f float64) {
          v.X = v.X * f
          v.Y = v.Y * f
      }
    
      func main() {
    
          v := Vertex{3, 4}
          Scale(&v, 10)
          fmt.Println(Abs(v))     //结果为50
      }
    

    示例二

      package main
    
      import (
          "fmt"
          "math"
      )
    
      type Vertex struct {
          X, Y float64
      }
    
      func Abs(v Vertex) float64 {
          return math.Sqrt(v.X*v.X + v.Y*v.Y)
      }
    
      func Scale(v Vertex, f float64) {
          v.X = v.X * f
          v.Y = v.Y * f
      }
    
      func main() {
    
          v := Vertex{3, 4}
          Scale(v, 10)
          fmt.Println(Abs(v))     //结果为5
      }
    

    (5)和(6)的四个例子又可以总结出:带指针参数的__函数__必须接受一个指针,但是__以指针为接收者__的方法被调用时,接收者既能为值又能为指针(Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5));

    同样的,对于值参数的函数或值作为接收者的方法,也遵循上面的规律(p.Abs() 会被解释为 (*p).Abs())

    (7) 使用指针接收者的__优势__

    方法能够修改其接收者指向的值

    可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效

    通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用

    (8) 无论是普通的函数还是带方法接收者的函数,都是__用指针才能改变指向的值,否则改的只是副本的值__

  • 接口

    (1) 接口类型 是由__一组方法签名定义的集合__,接口类型的变量可以保存任何实现了这些方法的值

      package main
    
      import (
          "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 float64
    
      func (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)
      }
    

    (2) 类型通过实现一个接口的所有方法来实现该接口,无需专门显式声明

    实现接口的方式既可以靠__值类型的方法接收者__,也可以靠__指针类型的方法接收者__,它们都可以视为实现了接口,只不过用的时候要对应上(值对应值,指针对应指针)

      package main
    
      import "fmt"
    
      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()
      }
    

    (3) 接口也是值,可以像其它值一样传递,接口值可以用作函数的参数或返回值

    在内部,接口值可以看做包含值和具体类型的元组:(value, type)

    接口值保存了一个具体底层类型的具体值。

    接口值调用方法时会执行其底层类型的同名方法

      package main
    
      import (
          "fmt"
          "math"
      )
    
      type I interface {
          M()
      }
    
      type T struct {
          S string
      }
    
      func (t *T) M() {
          fmt.Println(t.S)
      }
    
      type F float64
    
      func (f F) M() {
          fmt.Println(f)
      }
    
      func main() {
    
          var i I
    
          i = &T{"Hello"}
          describe(i)
          i.M()
    
          i = F(math.Pi)
          describe(i)
          i.M()
      }
    
      func describe(i I) {
          fmt.Printf("(%v, %T)\n", i, i)
      }
    

    (4) 即便接口内的具体值为 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 main() {
    
          var i I
    
          var t *T
          i = t
          describe(i)
          i.M()
    
          i = &T{"hello"}
      	describe(i)
          i.M()
      }
    
      func describe(i I) {
          fmt.Printf("(%v, %T)\n", i, i)
      }
    

    (5) 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)
      }
    

    (6) 空接口

    指定了零个方法的接口值被称为__空接口interface{}__

    1° 空接口可保存任何类型的值,包括基本类型(因为每个类型都至少实现了零个方法)

    2° 空接口被用来处理未知类型的值。例如,fmt.Print 可接受类型为 interface{} 的任意数量的参数(func Print(a …interface{}) (n int, err error){xxx})

      package main
    
      import "fmt"
    
      func main() {
    
          var i interface{}
          describe(i)
    
          i = 42
          describe(i)
    
          i = "hello"
          describe(i)
      }
    
      func describe(i interface{}) {
          fmt.Printf("(%v, %T)\n", i, i)
      }
    

    (7) 类型断言

      t := i.(T)
    

    该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。

    若 i 并未保存 T 类型的值,该语句就会触发一个恐慌panic。

    2° 为了判断一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。

      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)
    
          s, ok := i.(string)
          fmt.Println(s, ok)
    
          f, ok := i.(float64)
          fmt.Println(f, ok)
    
          f = i.(float64) // 报错(panic)
          fmt.Println(f)
      }
    

    (8) 类型选择:是一种按顺序从几个类型断言中选择分支的结构

      package main
    
      import "fmt"
    
      func do(i interface{}) {
    
          switch v := i.(type) {
          case int:
              fmt.Printf("Twice %v is %v\n", v, v*2)
          case string:
              fmt.Printf("%q is %v bytes long\n", v, len(v))
          default:
              fmt.Printf("I don't know about type %T!\n", v)
          }
      }
    
      func main() {
    
          do(21)
          do("hello")
          do(true)
      }
    
  • Stringer

    (1) fmt 包中定义的 Stringer 是最普遍的接口之一

      type Stringer interface {
          String() string
      }
    

    (2) Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值

      package main
    
      import "fmt"
    
      type MyType struct {
          X int
      }
    
      type Person struct {
          Id MyType
          Name string
          Age  int
      }
    
      func (p Person) String() string {
          return fmt.Sprintf("%v (%v years)%v", p.Name, p.Age, p.Id.X)
      }
    
      func main() {
    
          fmt.Println(1)
    
          a := Person{MyType{1},"Arthur Dent", 42}
          fmt.Println(a)
      }
    
  • 异常error

    (1) 和fmt.Stringer一样,error是一种__内建类型接口__

      type error interface {
          Error() string
      }
    

    (2) 函数经常会返回一个error,所以一般要判断一下error是否为nil,如果不是就要处理它

      i, err := strconv.Atoi("42")
      if err != nil {
          fmt.Printf("couldn't convert number: %v\n", err)
          return
      }
    
      fmt.Println("Converted integer:", i)
    
  • Readers

    (1) Reader是一个接口

      type Reader interface {
          Read(p []byte) (n int, err error)
      }
    

    (2) Go的标准库提供了多种Reader接口的实现,包括文件、网络、密码等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值