1 package main 2 import ( 3 "fmt" 4 "math" 5 ) 6 7 type Vertex struct { 8 X, Y float64 9 } 10 func (v Vertex) Abs() float64 {//abs()函数有一个vertex类型的接收者 11 return math.Sqrt(v.X*v.X + v.Y*v.Y)//3*3+4*4=25,25开方=5 12 }
13 func main() { 14 v := Vertex{3, 4}//传参 15 fmt.Println(v.Abs()) 16 }

方法即函数
记住:方法只是个带接收者参数的函数。
现在这个 Abs 的写法就是个正常的函数,功能并没有什么变化。
1 type Vertex struct { 2 X, Y float64 3 } 4 5 6 func Abs(v Vertex) float64 { 7 return math.Sqrt(v.X*v.X + v.Y*v.Y) 8 } 9 10 11 func main() { 12 v := Vertex{3, 4} 13 fmt.Println(Abs(v)) 14 }
你也可以为非结构体类型声明方法。
在此例中,我们看到了一个带 Abs 方法的数值类型 MyFloat。
你只能为在同一包内定义的类型的接收者声明方法,而不能为其它包内定义的类型(包括 int 之类的内建类型)的接收者声明方法。
(译注:就是接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。)
1 package main 2 import ( 3 "fmt" 4 "math" 5 ) 6 7 type MyFloat float64 8 //接收者的类型定义和方法声明在同一包内 9 10 func (f MyFloat) Abs() float64 {//转换符号,将负数转成正数 11 if f < 0 { 12 return float64(-f) 13 } 14 return float64(f) 15 } 16 17 func main() { 18 f := MyFloat(-math.Sqrt2) 19 fmt.Println(-math.Sqrt2) 20 fmt.Println(f) 21 fmt.Println(f.Abs()) 22 }
指针接收者
你可以为指针接收者声明方法。
这意味着对于某类型 T,接收者的类型可以用 *T 的文法。(此外,T 不能是像 *int 这样的指针。)[不能是指针的指针]
例如,这里为 *Vertex 定义了 Scale 方法。
指针接收者的方法可以修改接收者指向的值(就像 Scale 在这做的)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。
试着移除第 16 行 Scale 函数声明中的 *,观察此程序的行为如何变化。
若使用值接收者,那么 Scale 方法会对原始 Vertex 值的副本进行操作。(对于函数的其它参数也是如此。)Scale 方法必须用指针接受者来更改 main 函数中声明的 Vertex 的值。
1 package main 2 3 4 import ( 5 "fmt" 6 "math" 7 ) 8 9 10 type Vertex struct { 11 X, Y float64 12 } 13 14 15 func (v Vertex) Abs() float64 { 16 return math.Sqrt(v.X*v.X + v.Y*v.Y) 17 } 18 19 20 func (v *Vertex) Scale(f float64) { 21 //当给*Vertex定义scale方法,运行时为v的x为30,y为40v.abs()为30*30—+40*40=2500的开方为50 22 //当给Vertex定义scale方法,运行scale时操作的是vertex的副本,不改变原始值。运行v.abs()时v的x为x仍为3,y仍为4,因此开方得5 23 v.X = v.X * f 24 v.Y = v.Y * f 25 } 26 27 28 func main() { 29 v := Vertex{3, 4} 30 v.Scale(10) 31 fmt.Println(v.Abs()) 32 }
指针与函数
现在我们要把 Abs 和 Scale 方法重写为函数。
同样,我们先试着移除掉第 16 的 *。你能看出为什么程序的行为改变了吗?要怎样做才能让该示例顺利通过编译?
1 package main 2 3 4 import ( 5 "fmt" 6 "math" 7 ) 8 9 10 type Vertex struct { 11 X, Y float64 12 } 13 14 15 func Abs(v Vertex) float64 { 16 return math.Sqrt(v.X*v.X + v.Y*v.Y) 17 } 18 19 20 func Scale(v *Vertex, f float64) { 21 v.X = v.X * f 22 v.Y = v.Y * f 23 } 24 25 26 func main() { 27 v := Vertex{3, 4} 28 Scale(&v, 10) 29 fmt.Println(Abs(v)) 30 }
【移除*后报错如下,去掉主函数的scale函数调用时的传参(&v,10)中的&编译通过】

【可知,方法和参数要对应好】
方法与指针重定向
【方法和函数,最显见的区别是调用时的文法不同】
1 func (v *Vertex) Scale(f float64) { 2 //当接收者是指针时的方法被调用时,无论是值还是指针都能调用。如下main函数中的v为值可以调用scale方法,而p为指针也能直接调用scale方法。Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5) 3 v.X = v.X * f 4 v.Y = v.Y * f 5 } 6 7 8 func ScaleFunc(v *Vertex, f float64) { 9 //带指针参数的函数必须传入的是指针类型的参数,如下main函数中的v是vertex类型,在调用此函数时传入的是(&v,10) 10 //而非指针调用函数时传入须传入指针 11 v.X = v.X * f 12 v.Y = v.Y * f 13 } 14 15 16 func main() { 17 v := Vertex{3, 4} 18 v.Scale(2) 19 ScaleFunc(&v, 10) 20 21 22 p := &Vertex{4, 3} 23 p.Scale(3)//方法的文法 24 ScaleFunc(p, 8)//函数的文法 25 26 27 fmt.Println(v, p) 28 }
函数调用接收传参时也是诸如此类
1 func (v Vertex) Abs() float64 {//接收者为vertex类型的方法 2 return math.Sqrt(v.X*v.X + v.Y*v.Y) 3 } 4 5 6 func AbsFunc(v Vertex) float64 {//传入的为vertex类型的值 7 return math.Sqrt(v.X*v.X + v.Y*v.Y) 8 } 9 10 11 func main() { 12 v := Vertex{3, 4} 13 fmt.Println(v.Abs()) 14 fmt.Println(AbsFunc(v)) 15 16 17 p := &Vertex{4, 3} 18 fmt.Println(p.Abs())//指针类型也可直接调用abs()方法 19 fmt.Println(AbsFunc(*p))//指针类型要转化为值传参 20 }
选择值或指针作为接收者
使用指针接收者的原因有二:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。
在本例中,Scale 和 Abs 接收者的类型为 *Vertex,即便 Abs 并不需要修改其接收者。
通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。(我们会在接下来几页中明白为什么。)
1 func (v *Vertex) Scale(f float64) { 2 v.X = v.X * f 3 v.Y = v.Y * f 4 } 5 6 7 func (v *Vertex) Abs() float64 { 8 return math.Sqrt(v.X*v.X + v.Y*v.Y) 9 } 10 11 12 func main() { 13 v := &Vertex{3, 4} 14 fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())//3*3+4*4=25开方=5 15 v.Scale(5)//3*5=15,4*5=20=35 16 fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())//15平方+20平方的开方得25 17 }

浙公网安备 33010602011771号