Skip to content

Methods #11

@oufeng

Description

@oufeng
  • Methods: 有receiverfunction

    go没有class的概念,可以通过给数据类型扩展Methods的方式实现类似class的功能

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

    任何数据类型都可以扩展Methods, 在同一个package可用

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

    receiver作为结构体指针,可以更改参数的值

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

    普通function的参数类型和实际传参的类型要一致

    function参数为结构体指针, 传递进来的参数必须是结构体指针

    func ScaleFunc(v *Vertex, f float64) {
        v.X = v.X * f
        v.Y = v.Y * f
    }
    
    var v Vertex
    ScaleFunc(v, 5)  // Compile error!
    ScaleFunc(&v, 5) // OK
    

    function参数为结构体,传递进来的参数必须是结构体

    func AbsFunc(v Vertex) float64 {
        return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
    var v Vertex
    fmt.Println(AbsFunc(v))  // OK
    fmt.Println(AbsFunc(&v)) // Compile error!
    

    Methods在调用时会做自动转换

    Calling a method with a pointer receiver by an object instead of a pointer to it?

    receiver是结构体指针,v.Scale(5) as (&v).Scale(5)

    func (v *Vertex) Scale(f float64) {
        v.X = v.X * f
        v.Y = v.Y * f
    }
    
    var v Vertex 
    v.Scale(5)  // OK
    p := &v
    p.Scale(10) // OK
    

    receiver是结构体,p.Abs() as (*p).Abs()

    func (v Vertex) Abs() float64 {
        return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
    var v Vertex
    fmt.Println(v.Abs()) // OK
    p := &v
    fmt.Println(p.Abs()) // OK
    

    receiver为结构体指针的情况更常用,推荐

    可以修改receiver本身的值
    每次调用都会对receiver进行值拷贝, 拷贝结构体指针比拷贝结构体更节省资源,效率更高

  • Interfaces: Methods signatures

    Assignability

    接口是隐式实现的,不用implements等关键字

    package main
    
    import "fmt"
    
    type I interface {
        M()
    }
    
    type T struct {
        S string
    }
    
    // This method means type T implements the interface I,
    // but we don't need to explicitly declare that it does so.
    func (t T) M() {
        fmt.Println(t.S)
    }
    
    func main() {
        var i I = T{"hello"}
        i.M()
    }
    

    接口可以作为函数参数类型,未赋值时是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)
    }
    
    (<nil>, <nil>)
    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x49a62f]
    

    指针和接口的一些疑问

    理解 Go interface 的 5 个关键点
    指针和接口

    • 参数类型是结构体,实际传参也是结构体。实际参数的值并没有发生改变

      package main
      
      import "fmt"
      
      type I interface {
          Double()
      }
      
      type T struct {
          X int
          Y int
      }
      
      func (t T) Double() {
          t.X = t.X * 2
          t.Y = t.Y * 2
      }
      
      func main() {
          var i I = T{3,4}
          i.Double()
          fmt.Println(i)
      }
      

      正常,没话好说

    • 参数类型是结构体指针,实际传参也是结构体指针。实际参数的值发生了改变

      func (t *T) Double() {
          t.X = t.X * 2
          t.Y = t.Y * 2
      }
      
      func main() {
          var i I = &T{3,4}
          i.Double()
          fmt.Println(i)
      }
      

      正常,没话好说

    • 参数类型是结构体,实际传参是结构体指针。实际参数的值并没有发生改变

      func (t T) Double() {
          t.X = t.X * 2
          t.Y = t.Y * 2
      }
      
      func main() {
          var i I = &T{3,4}
          i.Double()
          fmt.Println(i)
      }
      

      正常,可以说一下

      实际传到函数里面是结构体指针的复制品
      虽然是赋值品,由于是指针的关系,可以间接找到真正的结构体
      只要编译器可以通过手段找到真正的结构体,就能正常调用
      毕竟go语言的哲学是简洁、灵活

    • 参数类型是结构体指针,实际传参是结构体

      func (t *T) Double() {
          t.X = t.X * 2
          t.Y = t.Y * 2
      }
      
      func main() {
          var i I = T{3,4}
          i.Double()
          fmt.Println(i)
      }
      
      cannot use T literal (type T) as type I in assignment:
      T does not implement I (Double method has pointer receiver)
      

      编译错误,着重讲一下

      实际传到函数里面是结构体的复制品

      靠谱的解释(还是有点隐晦)

      对于 T{3,4} 来说,这意味着 Double 方法会接受一个全新的 T{},因为方法的参数是 *T,编译器不会无中生有创建一个新的指针;即使编译器可以创建新指针,这个指针指向的也不是最初调用该方法的结构体

      不妨换个思路,假设一下如果编译通过并正常调用会出现什么情况?

      最终会输出{3 4}
      外面的实际参数的值并没有发生改变
      因为Double内部实际更改的是结构体复制品的值

      这样会带来怎样的后果?

      开发者估计傻眼了。接收者不是一个指针的类型吗?为什么外面I变量的值没有跟着改变?
      go语言的哲学是简洁、灵活。一连串的疑问与go语言的哲学理念就相违背了。
      所以还不如直接编译不通过,将众多疑问扼杀在摇篮中。

    空接口: 用来处理未知数据类型的参数

    空接口可以接受任何数据类型的参数(任何数据类型都有0个Methods)

    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)
    }
    
    (<nil>, <nil>)
    (42, int)
    (hello, string)
    

    类型断言

    检查接口变量i是否为nil
    检查接口变量i存储的值是否为某个类型
    如果类型不相符,okfalse,并且转换之后的值为默认值。并不会发生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)
    }
    
    hello
    hello true
    0 false
    panic: interface conversion: interface {} is string, not float64
    

    Type switches

    可以很方便对接口的数据类型求switch

    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)
    }
    
    Twice 21 is 42
    "hello" is 5 bytes long
    I don't know about type bool!
    

    String接口

    package main
    
    import "fmt"
    
    type Person struct {
        Name string
        Age  int
    }
    
    func (p Person) String() string {
        return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
    }
    
    func main() {
        a := Person{"Arthur Dent", 42}
        z := Person{"Zaphod Beeblebrox", 9001}
        fmt.Println(a, z)
    }
    

    Exercise: Stringers

    package main
    
    import (
        "fmt"
        "strings"
    )
    
    type IPAddr [4]byte
    
    // TODO: Add a "String() string" method to IPAddr.
    func (ip IPAddr) String() string {
        s := make([]string, len(ip))
        for i, val := range ip {
            s[i] = fmt.Sprint(int(val))
        }	
        return strings.Join(s, ".")
    }
    
    func main() {
        hosts := map[string]IPAddr{
            "loopback":  {127, 0, 0, 1},
            "googleDNS": {8, 8, 8, 8},
        }
        for name, ip := range hosts {
            fmt.Printf("%v: %v\n", name, ip)
        }
    }
    

    Errors接口

    函数通常会返回error。当error不为空时,调用方需要进行处理

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

    Exercise: Errors

    package main
    
    import (
        "fmt"
        "math"
    )
    
    
    type ErrNegativeSqrt float64
    
    func (e ErrNegativeSqrt) Error() string {
        return fmt.Sprintf("cannot Sqrt negative number: %f", e)
    }
    
    
    const e = 1e-8  // small delta
    
    func Sqrt(x float64) (float64, error) {
        if x < 0 {
            return 0, ErrNegativeSqrt(x)
        }
        z := x  // starting point
        for {
            new_z := z - ((z*z - x) / (2*z))
            if math.Abs(new_z - z) < e {
                return new_z, nil
            }
            z = new_z
        }
    }
    
    
    func main() {
        fmt.Println(Sqrt(2))
        fmt.Println(Sqrt(-2))
    }
    

    Readers接口

    读取stream

    package main
    
    import (
        "fmt"
        "io"
        "strings"
    )
    
    func main() {
        r := strings.NewReader("Hello, Reader!")
    
        b := make([]byte, 8)
        for {
            n, err := r.Read(b)
            fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
            fmt.Printf("b[:n] = %q\n", b[:n])
            if err == io.EOF {
                break
            }
        }
    }
    

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions