• 第六章方法


    • 方法声明

    方法的声明和普通函数的声明类似,只是在函数名字前面多了一个参数,这个参数把这个方法绑定到这个参数对应的类型上

    package geometry
    
    import (
        "fmt"
        "math"
    )
    
    type Point struct {
        X, Y float64
    }
    //普通的函数
    func Distance(p, q Point) float64 {
        return math.Hypot(q.X - p.X, q.Y - p.Y)
    }
    //Point类型的方法,附加的参数p称为方法的接收者,它源自早先的面向对象语言,用来描述主调方法就像向对象发送消息
    //Go语言中,接收者不使用特殊名(比如this或者self),而是我们自己选择接收者名字,就像其他的参数变量一样
    //调用方法的时候,接收者在方法名的前面,这就和声明保持一致
    //方法 func (p Point) Distance(q Point) float64 { return math.Hypot(q.X - p.X, q.Y - p.Y) } func main(){ p := Point{1, 2} q := Point{4, 6}
    fmt.Println(Distance(p, q))
    //包级别的函数
        //调用方法的时候,接收者在方法名的前面,这就和声明保持一致
        fmt.Println(p.Distance(q)) //Point类型的方法
        //表达式p.Distance称作选择子(selector),它为接收者p选择合适的Distance方法
        //选择子也用于选择结构类型中的某些字段值
        
        //编译器会通过方法名和接收者的类型决定调用哪一个函数
        //path[i-1]是Point类型,因此调用Point.Distance
        //perim是Path类型,因此调用Path.Distance
        perim := Path{
            {1, 1},
            {5, 1},
            {5, 4},
            {1, 1},
        }
        
        fmt.Println(perim.Distance())
    }
    
    //Path是连接多个点的直线段
    type Path []Point
    //Distance方法返回路径的长度
    func (path Path) Distance() float64 {
        sum := 0.0
        for i := range path {
            if i > 0 {
                sum += path[i-1].Distance(path[i])
            }
        }
        return sum
    }
    //Path是一个命名的slice类型,而非Point这样的结构体类型,但依旧可以给它定义方法
    //Go和许多其他面向对象的语言不同,可以将方法绑定到任何类型上
    //可以很方便地为简单的类型(如数字、字符串、slice、map,甚至函数等)定义附加的行为
    //同一个包下的任何类型都可以声明方法,只要它的类型既不是指针类型也不是接口类型

    使用方法的第一个好处:命名可以比函数更简短。在包的外部进行调用的时候,方法能够使用更加简短的名字且省略包的名字

    • 指针接收者的方法

    由于主调函数会复制每一个实参变量,如果函数需要更新一个变量,或者一个实参太大我们希望避免复制整个实参,因此必须使用指针来传递变量的地址。这也同样适用于更新接收者:将他绑定到指针类型,比如*Point

    func (p *Point) ScaleBy(factor float64) {
        p.X *= factor
        p.Y *= factor
    }
    
    //这个方法的名字是(*Point).ScaleBy
    //圆括号是必须的,没有圆括号,表达式会被解析为*(Point.ScaleBy)
    
    //在真实的程序中,习惯上遵循如果Point的任何一个方法使用指针接收者,那么所有的Point方法都应该使用指针接收者,即使有些方法并不一定需要
    
    //命名类型Point与指向它们的指针*Point是唯一可以出现在接收者声明处的类型。为防止混淆,不允许使用本身是指针的类型进行方法声明
    /**如果接收者p是Point类型的变量,但方法要求一个*point接收者,可以使用简写:*/
    p.ScaleBy(2)
    /**实际上编译器会对变量进行&p的隐式转换。只有变量才允许这么做,包括结构体字段,像p.X和数组或者slice的元素,比如perim[0]。不能对一个不能取地址的Point接收者参数调用*Point方法,因为无法获取临时变量的地址*/
    Point{1,2}.ScaleBy(2)//编译错误:不能获得Point类型字面量的地址
    
    //如果实参接收者是*Point类型,以Point.Distance的方式调用Point类型的方法是合法的,因为我们可以从地址中获取Point的值;只要解引用指向接收者的指针值即可。编译器自动插入一个隐式的*操作符
    
    pptr := &(Point{1, 2})
    
    pptr.Distance(q)  //等价于
    (*pptr).Distance(q)
    //在合法的方法调用表达式中,只有符合下面三种形式的语句才能够成立
    
    func (p Point) Distance(q Point) float64 {
        return math.Hypot(q.X - p.X, q.Y - p.Y)
    }
    
    func (p *Point) ScaleBy(factor float64) {
        p.X *= factor
        p.Y *= factor
    }
    
    //实参接收者和形参接收者是同一个类型
    Point{1,2}.Distance(q) //Point
    pptr.ScaleBy(2) //*Point 
    
    //实参接收者是T类型,形参接收者是*T类型。编译器会隐式地获取变量的地址
    p.ScaleBy(2)  //隐式转换为(&p)
    
    //实参接收者是*T类型,形参接收者是T类型。编译器会隐式地解析引用接收者,获得实际的值
    pptr.Distance(q) //隐式转换为(*pptr)

    如果所有类型T方法的接收者是T自己(而非*T),那么复制它的实例是安全的;调用方法的时候都必须进行一次复制。在任何方法的接收者是指针的情况下,应该避免复制T的实例,因为这么做可能会破坏内部原本的数据

    nil是一个合法的接收者:就像一些函数允许nil指针作为实参,方法的接收者也一样,尤其是当nil是类型中有意义的零值(如map和slice类型)时,更是如此

    //IntList是整型链表
    //*IntList的类型nil代表空列表
    type IntList struct {
        Value int
        Tail *IntList
    }
    
    //Sum返回列表元素的总和
    func (list *IntList) Sum() int {
        if list == nil {
            return 0
        }
        return list.Value + list.Tail.Sum()
    }
    
    //当定义一个类型允许nil作为接收者时,应当在文档注释中显式地标明
    // net/url包种Values类型的部分定义:
    
    package url
    //Values映射字符串到字符串列表
    type Values map[string][]string
    
    //Get返回第一个具有给定key的值
    //如不存在,则返回空字符串
    func (v values) Get(key string) string {
        if vs := v[key];len(vs)>0{
    return vs[0]
    }
    return "" }

    //Add添加一个键值到对应key列表中
    func (v Values) Add(key, value string) {
    v[key] = append(v[key], value)
    }

     它的实现是map类型但也提供了一系列方法来简化map的操作,它的值是字符串slice,即一个多重map。使用者可以使用它固有的操作方式(make、slice字面量、m[key]),或者使用它的方法,或者同时使用:

    m := url.Values{"lang": {"en"}}  //直接构造
    m.Add("item", "1")
    m.Add("item", "2")
    
    fmt.Println(m.Get("lang"))
    fmt.Println(m.Get("q"))
    fmt.Println(m.Get("item"))
    fmt.Println(m["item"])
    
    m = nil
    fmt.Println(m.Get("item"))
    fmt.Println(m["item"])
    
    m = nil
    fmt.Println(m.Get("item"))
    m.Add("item", "3")  //宕机:赋值给空的map类型
    //在最后一个Get调用中,nil接收者充当一个空map。它可以等同地写成Values(nil),Get("item"),但是nil.Get("item")不能通过编译,因为nil的类型没有确定。最后的Add方法会发生宕机因为它尝试更新一个空的map
    //因为url.Values是map类型而且map间接地指向它的键/值对,所以url.Values.Add对map中元素的任何更新和删除操作对调用者都是可见的。然而,和普通函数一样,方法对引用本身做的任何改变,比如设置url.Values为nil或者使它指向一个不同的map数据结构,都不会在调用者身上产生作用??
    • 通过结构体内嵌组成类型
    type Point struct{ X, Y float64 }
    
    type ColoredPoint struct {
        Point
        Color color.RGBA
    }
    
    //内嵌类型Point,和ColoredPoint并无继承关系
    //需要接收Point类型的参数时,如果传入了一个ColoredPoint类型的参数,会报错
    //编译错误:不能将ColoredPoint转换为Point类型

    内嵌的字段会告诉编译器生成额外的包装方法来调用Point声明的方法,相当于:

    func (p ColoredPoint) Distance(q Point) float64 {
        return p.Point.Distance(q)
    }
    
    func (p *ColoredPoint) ScaleBy(factor float64) {
        p.Point.ScaleBy(factor)
    }

    匿名字段类型可以是个指向命名类型的指针,这时,字段和方法间接地来自于所指向的对象。这可以让我们共享通用的结构以及使对象之间的关系更加动态、多样化

    • 方法变量与表达式

    通常都在相同的表达式里使用和调用方法,就像在p.Distance()中,但是把两个操作分开也是可以的。选择子p.Distance可以赋予一个方法变量,它是一个函数,把方法Point.Distance绑定到一个接收者p上。函数只需要提供实参而不需要提供接收者就能够调用

    p := Point{1, 2}
    q := Point{4, 6}
    distanceFromP := p.Distance //方法变量   选择子 p.Distance
    fmt.Println(distanceFromP(q))
    var origin Point
    fmt.Println(distanceFromP(origin))
    
    scaleP := p.ScaleBy
    scaleP(2)  //p变成(2,4)

    如果包内的API调用一个函数值,并且使用者期望这个函数的行为是调用一个特定接收者的方法,方法变量就非常有用

    与方法变量相关的是方法表达式。和调用普通的函数不同,在调用方法的时候必须提供接收者,并且按照选择子的语法进行调用。而方法表达式写成T.f或者(*T).f,其中T是类型,是一种函数变量,把原来方法的接收者替换成函数的第一个形参,因此它可以像平常的函数一样调用

    p := Point{1, 2}
    q := Point{4, 6}
    distance := Point.Distance //方法表达式
    T.f或者(*T).f,其中T是类型,是一种函数变量,把原来方法的接收者替换成函数的第一个形参
    fmt.Println(distance(p, q))
    fmt.Printf("%T ", distance)

    scale := (*Point).ScaleBy
    scale(&p, 2)
    fmt.Println(p)
    fmt.Printf("%T ", scale)

    //如果需要用一个值来代表多个方法中的一个,而方法都属于同一个类型,方法变量可以帮助你调用这个值所对应的方法来处理不同的接收者
    type Point struct { X, Y float64}
    
    func (p Point) Add(q Point) Point {
        return Point{
            
        }
    }
    • 示例:位向量
    • 封装
  • 相关阅读:
    boost::timer库使用
    List的子类特点
    Exception in thread "main" java.util.ConcurrentModificationException解决方案
    Java中的集合Collection
    数组和集合的区别
    jsp 九大内置对象
    include指令与jsp:include动作标识的区别
    idea 2018注册码(激活码)
    内存的页面置换算法
    MYSQL的两种存储引擎区别
  • 原文地址:https://www.cnblogs.com/liushoudong/p/13039294.html
Copyright © 2020-2023  润新知