江苏中南建设投标网站,网站后台 语言,设计师网红,注册公司的流程和步骤文章目录 方法方法声明基于指针对象的方法nil 也是合法的接收器类型 通过嵌入结构体来扩展类型方法值和方法表达式封装 方法
今天我们来复习 Golang 语法中的“方法”#xff0c;方法属于某个「类型」#xff0c;它属于 Go 的语法糖#xff0c;本质上是一个函数#xff0c… 文章目录 方法方法声明基于指针对象的方法nil 也是合法的接收器类型 通过嵌入结构体来扩展类型方法值和方法表达式封装 方法
今天我们来复习 Golang 语法中的“方法”方法属于某个「类型」它属于 Go 的语法糖本质上是一个函数使得这个类型可以像在调用它的“类方法”一样来调用这个函数。
方法声明
在声明函数时在函数名前面放上一个变量这个函数就成为了变量所对应类型的方法这个方法将成为该类型的独占方法。一个方法声明的例子如下
type Point struct{ X, Y float64 }func Distance(p, q Point) float64 {return math.Hypot(q.X-p.X, q.Y-p.Y)
}func (p Point) Distance(q Point) float64 {return math.Hypot(q.X-p.X, q.Y-p.Y)
}上述方法定义时Point类型的p是方法的接收器receiver在 C 当中我们使用this作为接收器而在 Python 中我们使用self作为接收器。在 Golang 当中我们可以自定义接收器的名称《Go 语言圣经》当中给出的建议是使用类型的第一个字母。
调用方法的例子如下
p : Point{1, 2}
q : Point{4, 6}
fmt.Println(Distance(p, q)) // 5, function call
fmt.Println(p.Distance(q)) // 5, method call可以看到函数Distance与Point类型的Distance方法不会产生冲突。
在 Go 当中美中类型都有其方法的命名空间我们在使用 Distance 这个名字的时候不同的 Distance 调用指向了不同类型里的 Distance 方法。下例是一个更复杂的例子它定义了一个Path类型它本质是[]Point我们进一步为它也定义一个Distance方法
// A Path is a journey connecting the points with straight lines.
type Path []Point
// Distance returns the distance traveled along the path.
func (path Path) Distance() float64 {sum : 0.0for i : range path {if i 0 {sum path[i-1].Distance(path[i])}}return sum
}两个Distance有不同的类型但两个方法之间没有任何关系。调用新方法来计算三角形周长
perim : Path{{1, 1},{5, 1},{5, 4},{1, 1},
}
fmt.Println(perim.Distance()) // 12基于指针对象的方法
在最开始我们提到「方法」是 Golang 的语法糖其底层本质上仍然是一个函数方法的接收器将作为这个函数的第一个参数。因此如果我们想要在方法当中修改接收器的值或是这个方法的接收器占用的内存非常大我们不希望在调用方法时进行值拷贝此时就可以使用指针对象作为接收器
func (p *Point) ScaleBy(factor float64) {p.X * factorp.Y * factor
}方法的名字是(*Point).ScaleBy此处的括号是必须的否则会被理解为*(Point.ScaleBy)函数值的指针。
在真实开发环境中一般约定如果Point这个类型有「以指针作为接收器」的方法那么Point的所有方法必须有一个指针接收器即使某些方法不需要指针接收器。此外为了避免歧义如果一个类型本身是一个指针的话比如type P *int那么它不允许出现在接收器中。
想要调用指针类型方法(*Point).ScaleBy只需要提供一个Point类型的指针并调用该方法即可。比如
r : Point{1, 2}
r.ScaleBy(2)
fmt.Println(*r) // {2, 4}以上的写法较为笨拙因为需要我们显式地将值转为指针再调用指针接收器方法。 Go 存在一种语法糖也就是如果一个值类型直接调用它的以指针作为接收器的方法那么 Go 的编译器会隐式地帮我们用这个值的指针来调用指针方法。比如
p : Point{1, 2}
p.ScaleBy(3)上述代码中编译器隐式地帮我们用p调用ScaleBy。这种简写方法仅适用于变量因为变量的地址是确定的如果Point是某个 struct 的成员或者是 slice 当中的元素由于我们无法取到 struct 成员的地址且 slice 底层的数组可能会修改从而导致地址改变因此对于这类值我们不能调用它们的以指针作为接收器的方法。临时变量的内存地址也无法取到因此也不能直接对临时变量调用指针接收器方法
Point{1, 2}.ScaleBy(2) // ❌ 不能对临时变量调用指针接收器方法此外对于一个指针类型如果它具有以值作为接收器的方法那么这个指针也可以直接调用值接收器方法Go 编译器会隐式地帮我们解指针引用。
总结一下在每一个合法的方法调用表达式中存在以下三种情况都是可以正常运行的
第一种情况是方法调用者的类型与其方法接收器的类型匹配即二者都是值T或指针*T
Point{1, 2}.Distance(q) // Distance 是以值为接收器的方法
pptr.ScaleBy(2) // pptr 是 Point 的指针, ScaleBy 是以指针为接收器的方法第二种是如果接收器的实参即方法的调用者类型是值T但接收器的形参类型是*T这种情况下编译器会隐式地帮助我们取实参的地址
p.ScaleBy(2) // implicit (p)第三种是如果接收器的实参是指针*T形参是T编译器会隐式地帮助我们解引用取到指针实际指向的变量值
pptr.Distance(q) // implicit (*pptr)nil 也是合法的接收器类型
就像函数允许 nil 值的指针作为参数一样方法本质上也是函数因此该类型的指针接收器方法可以通过 nil 指针来调用。
下例是一个链表求和的例子该例通过调用链表类型的 Sum 方法来对链表进行求和由于 nil 指针也可以调用对应类型的方法因此当链表到尾时nil 仍然可以继续调用 Sum 方法只不过这次调用会在方法的逻辑中判断出当前指针为 nil返回 0
// An IntList is a linked list of integers.
// A nil *IntList represents the empty list.
type IntList struct {Value intTail *IntList
}
// Sum returns the sum of the list elements.
func (list *IntList) Sum() int {if list nil {return 0}return list.Value list.Tail.Sum()
}通过嵌入结构体来扩展类型
下例定义了一个 ColoredPoint 类型它将 Point 类型作为嵌入加入到了结构体的定义当中
type Point struct { X, Y int64 }
type ColoredPoint struct {PointColor color.RGBA
}基于结构体内嵌的方式我们可以直接认为嵌入的字段就是 ColoredPoint 自己的字段在使用时完全不需要指出 PointColoredPoint 本身就可以直接访问 X 和 Y 成员
var cp ColoredPoint
cp.X 1
fmt.Println(cp.Point.X)
cp.Point.Y 2
fmt.Println(cp.Y)对于 Point 中的方法我们也有类似的用法可以把 ColoredPoint 类型当作接收器来调用 Point 里的方法即使 ColoredPoint 没有声明这些方法
red : color.RGBA{255, 0, 0, 255}
blue : color.RGBA{0, 0, 255, 255}
var p ColoredPoint{Point{1, 1}, red}
var q ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // 5
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // 10Point 类的方法也被引入了 ColoredPoint故内嵌可以使我们定义字段特别多的复杂类型可以先将字段按小类型分组然后定义小类型的方法之后再把它们组合起来。
需要注意的是Point 嵌入在了 ColoredPoint 当中这种关系不是继承也不是子类与父类之间的关系。ColoredPoint “has” a Point所以在调用 Distance 方法时方法传入的实参必须显式地选择 ColoredPoint 当中的 Point 对象否则编译器会报错compile error: cannot use q (ColoredPoint) as Point。
ColoredPoint 不是 Point但基于内嵌它拥有一个 Point并且从拥有的 Point 中引入了 Distance 和 ScaleBy 方法。从具体的实现角度来说内嵌字段会指导编译器隐式地额外生成方法来对已有的方法进行封装等价于
func (p ColoredPoint) Distance(q Point) float64 {return p.Point.Distance(q)
}func (p *ColoredPoint) ScaleBy(factor float64) {p.Point.ScaleBy(factor)
}因此即使我们通过 ColoredPoint 对象调用内嵌的 Point 的方法在 Point 的方法中我们也无法访问 ColoredPoint 的成员。
在类型中内嵌的匿名字段也可能是一个命名类型的指针这种情况下字段和方法会间接地引入到当前的类型中。添加这一层间接关系让我们可以共享通用的结构并动态地改变对象之间的关系。下例的 ColoredPoint 声明内嵌了一个*Point指针
type ColoredPoint struct {*PointColor color.RGBA
}p : ColoredPoint{Point{1, 1}, red}
q : ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(*q.Point)) // 5
q.Point p.Point // Now, p and q share the same Point
p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point) // {2, 2}, {2, 2}一个 struct 可以定义多个匿名字段例如
type ColoredPoint struct {Pointcolor.RGBA
}这种类型的值会拥有 Point 和 RGBA 类型的所有方法以及直接定义在 ColoredPoint 中的方法。当编译器解析一个选择器到方法时比如p.ScaleBy它会首先去找直接定义在这个类型当中的ScaleBy方法然后找 ColoredPoint 内嵌字段引入的方法然后去 Point 和 RGBA 的内嵌字段继续找引入的方法一直递归向下寻找直到找到为止。如果选择器有二义性的话编译器会报错比如你在同一级里有两个同名的方法。
下例展示了一个基于 Go 实现的非常简单的 Cache 的 Demo
var cache struct {sync.Mutexmapping map[string]string
}{mapping: make(map[string]string),
}func Lookup(key string) string {cache.Lock()v : cache.mapping[key]cache.Unlock()return v
}该例中sync.Mutex字段被嵌入到了 struct 当中故其 Lock 和 Unlock 方法也被引入到了 cache 对应的匿名结构类型使得我们可以非常方便地进行加锁和解锁操作。
方法值和方法表达式
我们之前使用过的p.Distance注意不带括号此时是方法的值叫做“选择器”选择器会返回一个方法“值”即一个将方法Point.Distance绑定到特定接收器变量的函数。这个函数调用时不需要指定接收器因为已经在p.Distance中指定p为接收器了此时只需要传入参数即可
p : Point{1, 2}
q : Point{4, 6}distanceFromP : p.Distance // p.Distance 获取方法值, 绑定到 distanceFromP 上
// ⬆️ 此时已经选择 p 为接收器了
fmt.Println(distanceFromP(q)) // 5当T是一个类型时方法表达式可能会写作T.f或(*T).f此时返回的是一个函数的“值”这种函数会将第一个传入的参数作为接收器例如
p : Point{1, 2}
q : Point{4, 6}
distance : Point.Distance
fmt.Println(distance(p, q))这一点不难理解因为我在本篇开头已经提到Golang 的 Method 实际上是一种语法糖它本质上是一个以方法调用者为第一个实参的函数。因此类型的方法值就是函数本身即
distance : Point.Distance
// ⬆️ distance 本身是一个有两个形参的函数, 这两个形参的类型都是 Point封装
“一个对象的变量或方法对调用方不可见”被定义为“封装”详细来说也可以称为“信息隐藏”。封装是面向对象的特性之一。
Go 只有一种可见性手段那就是大写首字母的标识符会从定义它们的包中被导出小写字母则不会导出。这种限制包内成员可见性的方式同样适用于 struct 或一个类型的方法。基于上述原因如果我们想对一个对象进行封装那么它必须是一个 struct。
下例定义了一个 IntSet 类型尽管它只有一个字段但是由于我们想要对它进行封装所以必须把这个单独的字段定义在 struct 当中
type IntSet struct {words []uint64
} // words 是非导出的, 用户无法直接访问// ⬇️ 如果我们直接定义为
type IntSet []uint64 // 该方法会使得其他包的用户直接改变 IntSet 底层的 []uint64这种基于名字的手段使得在 Golang 语言层面最小的封装单元是 package而不是其他语言一样的类型。一个 struct 类型的字段对同一个包内的所有代码都有可见性无论你的代码是写在一个函数还是一个方法里。
封装提供了三个优点
调用方不能直接修改对象的变量值而修改只能通过包的发布人员对外提供的接口来完成隐藏了实现的细节防止调用方以来那些可能变化的具体实现这使得设计包的程序员可以在不破坏对外 API 的情况下获得更多开发上的自由阻止外部调用方对对象内部的值任意地进行修改。
Go 的编程风格不禁止导出字段一旦导出就无法保证在 API 兼容的前提下去除对已经导出字段的导出。 文章转载自: http://www.morning.skdrp.cn.gov.cn.skdrp.cn http://www.morning.lgsfb.cn.gov.cn.lgsfb.cn http://www.morning.msbpb.cn.gov.cn.msbpb.cn http://www.morning.jfxth.cn.gov.cn.jfxth.cn http://www.morning.lnsnyc.com.gov.cn.lnsnyc.com http://www.morning.pqppj.cn.gov.cn.pqppj.cn http://www.morning.xrsqb.cn.gov.cn.xrsqb.cn http://www.morning.kndyz.cn.gov.cn.kndyz.cn http://www.morning.zkgpg.cn.gov.cn.zkgpg.cn http://www.morning.lnsnyc.com.gov.cn.lnsnyc.com http://www.morning.ypbdr.cn.gov.cn.ypbdr.cn http://www.morning.nxnrt.cn.gov.cn.nxnrt.cn http://www.morning.amonr.com.gov.cn.amonr.com http://www.morning.cwyrp.cn.gov.cn.cwyrp.cn http://www.morning.pznhn.cn.gov.cn.pznhn.cn http://www.morning.jwbfj.cn.gov.cn.jwbfj.cn http://www.morning.zcckq.cn.gov.cn.zcckq.cn http://www.morning.jwqqd.cn.gov.cn.jwqqd.cn http://www.morning.lblsx.cn.gov.cn.lblsx.cn http://www.morning.ykyfq.cn.gov.cn.ykyfq.cn http://www.morning.xrct.cn.gov.cn.xrct.cn http://www.morning.frxsl.cn.gov.cn.frxsl.cn http://www.morning.qfgwx.cn.gov.cn.qfgwx.cn http://www.morning.snlxb.cn.gov.cn.snlxb.cn http://www.morning.prsxj.cn.gov.cn.prsxj.cn http://www.morning.5-73.com.gov.cn.5-73.com http://www.morning.rhph.cn.gov.cn.rhph.cn http://www.morning.snyqb.cn.gov.cn.snyqb.cn http://www.morning.fmdvbsa.cn.gov.cn.fmdvbsa.cn http://www.morning.sfwcx.cn.gov.cn.sfwcx.cn http://www.morning.tfznk.cn.gov.cn.tfznk.cn http://www.morning.cfocyfa.cn.gov.cn.cfocyfa.cn http://www.morning.ltpzr.cn.gov.cn.ltpzr.cn http://www.morning.gtdf.cn.gov.cn.gtdf.cn http://www.morning.zcncb.cn.gov.cn.zcncb.cn http://www.morning.lxlfr.cn.gov.cn.lxlfr.cn http://www.morning.rjnky.cn.gov.cn.rjnky.cn http://www.morning.rdlfk.cn.gov.cn.rdlfk.cn http://www.morning.cpfx.cn.gov.cn.cpfx.cn http://www.morning.tpfny.cn.gov.cn.tpfny.cn http://www.morning.qflwp.cn.gov.cn.qflwp.cn http://www.morning.gtdf.cn.gov.cn.gtdf.cn http://www.morning.zwgbz.cn.gov.cn.zwgbz.cn http://www.morning.ctrkh.cn.gov.cn.ctrkh.cn http://www.morning.zzfqn.cn.gov.cn.zzfqn.cn http://www.morning.ffbp.cn.gov.cn.ffbp.cn http://www.morning.zwpzy.cn.gov.cn.zwpzy.cn http://www.morning.pdmml.cn.gov.cn.pdmml.cn http://www.morning.drcnf.cn.gov.cn.drcnf.cn http://www.morning.wjxtq.cn.gov.cn.wjxtq.cn http://www.morning.fzlk.cn.gov.cn.fzlk.cn http://www.morning.jlmrx.cn.gov.cn.jlmrx.cn http://www.morning.dmrjx.cn.gov.cn.dmrjx.cn http://www.morning.qpsft.cn.gov.cn.qpsft.cn http://www.morning.cwskn.cn.gov.cn.cwskn.cn http://www.morning.cxryx.cn.gov.cn.cxryx.cn http://www.morning.qrqdr.cn.gov.cn.qrqdr.cn http://www.morning.pbksb.cn.gov.cn.pbksb.cn http://www.morning.dqdss.cn.gov.cn.dqdss.cn http://www.morning.tfzjl.cn.gov.cn.tfzjl.cn http://www.morning.qmtzq.cn.gov.cn.qmtzq.cn http://www.morning.jnkng.cn.gov.cn.jnkng.cn http://www.morning.hous-e.com.gov.cn.hous-e.com http://www.morning.wrbnh.cn.gov.cn.wrbnh.cn http://www.morning.qgjp.cn.gov.cn.qgjp.cn http://www.morning.ylklr.cn.gov.cn.ylklr.cn http://www.morning.krqhw.cn.gov.cn.krqhw.cn http://www.morning.mzpd.cn.gov.cn.mzpd.cn http://www.morning.mznqz.cn.gov.cn.mznqz.cn http://www.morning.mngyb.cn.gov.cn.mngyb.cn http://www.morning.snbq.cn.gov.cn.snbq.cn http://www.morning.xnpj.cn.gov.cn.xnpj.cn http://www.morning.xkmrr.cn.gov.cn.xkmrr.cn http://www.morning.jlktz.cn.gov.cn.jlktz.cn http://www.morning.sjftk.cn.gov.cn.sjftk.cn http://www.morning.hnzrl.cn.gov.cn.hnzrl.cn http://www.morning.zdydj.cn.gov.cn.zdydj.cn http://www.morning.nkkr.cn.gov.cn.nkkr.cn http://www.morning.kvzvoew.cn.gov.cn.kvzvoew.cn http://www.morning.xtkw.cn.gov.cn.xtkw.cn