方法中的自己的总结
方法的声明
方法的声明和普通函数的声明类似,只是在函数名字前面多了一个参数。这个参数吧这个方法绑定到这个参数对应的类型上:
package main
import "math"
type Point struct {
X, Y float64
}
// 普通的函数
func Distance(q, p Point) float64{
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// Point类型方法
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)) // 5
fmt.Println(p.Distance(q)) // 5
}
上面两个Distance函数声明并不冲突,第一个声明一个包级别的函数,第二个声明一个类型Point的方法。它的名字是Point.Distance
p.Distance称作选择子,因为它为接收者p选择合适的Distance方法。
例子,定义一个path类型表示一条线,同样也用Distance作为方法名:
package main
import (
"math"
"fmt"
)
type Point struct {
X, Y float64
}
// 普通的函数
func Distance(q, p Point) float64{
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// Point类型方法
func (Q Point) Distance(q Point) float64 {
return math.Hypot(q.X-Q.X, q.Y-Q.Y)
}
type Path []Point
func (path Path) Distance() float64 {
sum := 0.0
for i := range path {
if i > 0 {
sum += path[i-1].Distance(path[i])
}
}
return sum
}
func main() {
a := Path{
{1, 1},
{5, 1},
{5, 4},
{1, 1},
}
fmt.Println(a.Distance()) // 12
}
Go语言可以将方法绑定到任何类型上,可以很方便的对想要的类型定义附加行为。编译器会通过方法名和接收者的类型决定调用哪一个函数。
不同的类型可以使用相同的方法名。
指针接收者的方法
由于主调函数会复制每一个实参变量,如果函数需要更新一个变量,或者如果一个实参太大而我们希望避免复制这个实参,因此我们必须使用指针来传递变量的地址。这也同样适用于更新接收者:我们将它绑定到指针类型:
package main
import (
"fmt"
)
type Point struct {
X, Y float64
}
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
func main() {
r := &Point{1, 2}
r.ScaleBy(2)
fmt.Println(*r) // {2 4}
}
也可以:
package main
import (
"fmt"
)
type Point struct {
X, Y float64
}
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
func main() {
r := Point{1, 2}
tr := &r
tr.ScaleBy(2)
fmt.Println(r) // {2 4}
}
nil是一个合法的接收者
方法允许nil作为实参,在整型数链表中,nil代表空链表:
package main
type IntList struct {
Value int
Tail *IntList
}
func (l *IntList) Sum() int {
if l == nil{
return 0
}
return l.Value + l.Tail.Sum()
}
在net/url包中Values类型的部分定义:
package main
// 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)
}
进行测试:
func main() {
m := Values{"lang":{"en"}}
m.Add("item", "1")
m.Add("item", "2")
fmt.Println(m.Get("lang")) // en
fmt.Println(m.Get("q"))//
fmt.Println(m.Get("item"))// 1
fmt.Println(m["item"]) // [1 2]
m = nil
fmt.Println(m.Get("item")) //
}
通过结构体内嵌组成类型
type Point struct {
X, Y float64
}
type ColoredPoint struct {
Point
Color color.RGBA
}
内嵌了一个Point类型,使结构体ColoredPoint包含有其它更多的字段,并且可以直接引用:
func main() {
var cp ColoredPoint
cp.X = 1
fmt.Println(cp.Point.X) // 1
cp.Point.Y = 3
fmt.Println(cp.Y) // 3
}
package main
import (
"image/color"
"fmt"
"math"
)
type Point struct {
X, Y float64
}
type ColoredPoint struct {
Point
Color color.RGBA
}
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
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)) // 10
}
可用ColoredPoint声明内嵌 *Point:
import (
"image/color"
"fmt"
"math"
)
type Point struct {
X, Y float64
}
type ColoredPoint struct {
*Point
Color color.RGBA
}
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
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.Point = q.Point // q和p共享一个Point
p.ScaleBy(2)
fmt.Println(*q.Point, *p.Point) // {10 8} {10 8}
}
方法只能在命名的类型(如 Point)和指向它们的指针( *Point)中声明,但内嵌帮助我们能够在未命名的结构体类型中声明方法
方法变量与表达式
方法变量:把方法绑定到一个接收者上,这样做有一个”拖延“的作用。:
package main
import (
"fmt"
"math"
)
type Point struct {
X, Y float64
}
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
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}
distanceFromP := p.Distance // 方法变量
fmt.Println(distanceFromP(q)) // 5
var origin Point // {0, 0}
fmt.Println(distanceFromP(origin)) // "2.23606797749979"(根号5)
scaleP := p.ScaleBy // 方法变量
scaleP(2)
fmt.Println(p.X,p.Y) // 2 4
scaleP(3)
fmt.Println(p.X,p.Y) // 6 12
scaleP(10)
fmt.Println(p.X,p.Y) // 60 120
}
与方法变量相关的是方法表达式,在调用方法时必须提供接收者,并按照选择子的语法进行调用,一般写作T.f或者(*T).f,其中T是类型:
package main
import (
"fmt"
"math"
)
type Point struct {
X, Y float64
}
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
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}
distance := Point.Distance // 方法表达式
fmt.Println(distance(p, q)) // 5
fmt.Printf("%T\n", distance) // func(main.Point, main.Point) float64
scale := (*Point).ScaleBy // 方法表达式
scale(&p, 2)
fmt.Println(p) // {2 4}
fmt.Printf("%T\n", scale) // func(*main.Point, float64)
}
如果需要用一个值来代表多个方法中的一个,而方法都属于同一个类型,方法变量可以帮助你调用这个值所对应的方法来处理不同的接收者。
type Point struct {
X, Y float64
}
func (p Point) Add(q Point) Point {
return Point{q.X+p.X, q.Y+p.Y}
}
func (p Point) Sub(q Point) Point {
return Point{p.X - q.X, p.Y - q.Y}
}
type Path []Point
func (path Path) TranslateBy(offset Point, add bool) {
var op func(p, q Point) Point
if add {
op = Point.Add
} else {
op = Point.Sub
}
for i := range path{
path[i] = op(path[i],offset)
}
}
根据bool值,选择是用path[i].Add(offset)或者是path[i].Sub(offset)。
示例_位向量
位向量使用一个无符号整型值的slice,每一位代表集合中的一个元素。如果设置第i位的元素,则认为集合包含i。
下面程序演示了一个含有三个方法的简单位向量类型。
- IntSet是一个包含非负整数的集合,零值代表空的集合
type IntSet struct {words []uint64} - Has方法的返回值表示是否存在非负数x
func (s *IntSet) Has(x int) bool { word, bit := x/64, uint(x%64) return word < len(s.words) && s.words[word]&(1<<bit) != 0 } - Add添加非负数x到集合中
func (s *IntSet) Add (x int) { word, bit := x/64, uint(x%64) for word >= len(s.words){ s.words = append(s.words, 0) } s.words[word] |= 1<<bit } - UnionWith将会对s和t做并集,并将结果存在S中
func (s *IntSet) UnionWith (t *IntSet) { for i, tword := range t.words{ if i < len(s.words){ s.words[i] |= tword } else { s.words = append(s.words, tword) } } }解释:因为是64位,为了定位x位的位置,使用商
x/64作为字的索引,而x%64记作该字内位的索引。操作符|=按位或(或运算是0-0-0,其他都是1,正好可以去重),计算一次64个元素求并集的结果。
写一个String好进行输出查看,String方法以字符串“{1, 2, 3}”的形式返回集中。
func (s *IntSet) String()string {
var buf bytes.Buffer
buf.WriteByte('{')
for i, word := range s.words{
if word == 0 {
continue
}
for j := 0; j < 64; j++ {
if word&(1<<uint(j)) != 0{
if buf.Len() > len("{"){
buf.WriteByte(' ')
}
fmt.Fprintf(&buf, "%d", 64*i+j)
}
}
}
buf.WriteByte('}')
return buf.String()
}
演示:
func main() {
var x, y IntSet
x.Add(1)
x.Add(144)
x.Add(9)
fmt.Println(x.String()) // {1 9 144}
y.Add(9)
y.Add(42)
fmt.Println(y.String()) // {9 42}
x.UnionWith(&y)
fmt.Println(x.String()) // {1 9 42 144}
fmt.Println(x.Has(9),x.Has(123)) // true false
}
封装
如果变量或方法是不能通过对象访问到的,这称作封装的变量或方法。
-
要封装一个对象,必须是结构体。
-
在Go中封装的单元是包而不是类型。无论是在函数内的代码还是方法内的代码,结构体类型内的字段对于同一个包中的多有代码都是可见的。
封装的优点
- 因为使用方不能直接修改对象的变量,所以不需要更多的语句用来检查变量的值。
- 隐藏细节可以防止使用方依赖的属性发生改变,使得设计者可以更加灵活地改变API的实现而不破坏兼容性。使用者使用时更多的感觉是性能的提升,而不关心其中的实现。
- 防止使用者肆意改变对象内的变量。