10.6.md 5.1 KB

10.6 方法

10.6.1 方法是什么

在Go中,结构体就像是类的一种简化形式,那么OO程序员可能会问:类的方法在哪里呢?在Go中有一个概念,它和方法有着同样的名字,并且大体上意思相同:Go方法是作用在接受者(receiver)上的一个函数,接受者是某种类型的变量。因此方法是一种特殊类型的函数。

接受者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是int、bool、string或数组的alias类型。但是接受者不能是一个接口类型(参考 第11章),因为接口是一个抽象定义,但是方法却是具体实现;如果这样做会引发一个编译错误:invalid receiver type…

最后接受者不能是一个指针类型,但是它可以是任何其他允许类型的指针。

一个类型加上它的方法等价于OO中的一个类。一个重要的区别是:在Go中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。

类型T(或*T)上的所有方法的集合叫做类型T(或*T)的方法集。

因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接受者类型,是有重载的:具有同样名字的方法可以在2个或多个不同的接受者类型上存在,比如在同一个包里这么做是允许的:

func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix

alias类型不能有它原始类型上已经定义过的方法。

定义方法的一般格式如下:

func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }

在方法名之前,func关键字之后的括号中指定receiver。

如果recv是receiver的实例,Method1是它的方法名,那么方法调用遵循传统的object.name选择器符号:recv.Method1()

如果recv一个指针,Go会自动解引用。

如果方法不需要使用recv的值,可以用*_*替换它,比如:

func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }

recv就像是OO语言中的this或self,但是Go中并没有这两个关键字。随个人喜好,你可以使用this或self作为receiver的名字。下面是一个结构体上的简单方法的例子:

Listing 10.10—method .go

package main

import "fmt"

type TwoInts struct {
	a int
	b int
}

func main() {
	two1 := new(TwoInts)
	two1.a = 12
	two1.b = 10

	fmt.Printf("The sum is: %d\n", two1.AddThem())
	fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20))

	two2 := TwoInts{3, 4}
	fmt.Printf("The sum is: %d\n", two2.AddThem())
}

func (tn *TwoInts) AddThem() int {
	return tn.a + tn.b
}

func (tn *TwoInts) AddToParam(param int) int {
	return tn.a + tn.b + param
}

输出:

The sum is: 22
Add them to the param: 42
The sum is: 7

下面是非结构体类型上方法的例子:

Listing 10.11—method2.go:

 package main

import "fmt"

type IntVector []int

func (v IntVector) Sum() (s int) {
	for _, x := range v {
		s += x
	}
	return
}

func main() {
	fmt.Println(IntVector{1, 2, 3}.Sum()) // 输出是6
}

** 练习 10.6: employee_salary.go

定义结构体employee,它有一个salary字段,给这个结构体定义一个方法giveRaise来按照指定的百分比增加薪水。

** 练习 10.7: iteration_list.go

下面这段代码有什么错?

package main

import "container/list"

func (p *list.List) Iter() {
	// ...
}

func main() {
	lst := new(list.List)
	for _= range list.Iter() {	
	}
}

类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在int、float或类似这些的类型上定义方法。试图在int类型上定义方法会得到一个编译错误:

cannot define new methods on non-local type int 

比如想在time.Time上定义如下方法:

func (t time.Time) first3Chars() string {
	return time.LocalTime().String()[0:3]
}

类型在在其他的,或是非本地的包里定义,在它上面定义方法都会得到和上面同样的错误。

但是有一个绕点的方式:可以先定义该类型(比如int,float)的别名类型,然后再为别名类型定义方法。或者像下面这样将它作为匿名类型嵌入在一个新的结构体中。当然方法只在这个别名类型上有效。

Listing 10.12—method_on_time.go:

package main

import (
	"fmt"
	"time"
)

type myTime struct {
	time.Time //anonymous field
}

func (t myTime) first3Chars() string {
	return t.Time.String()[0:3]
}
func main() {
	m := myTime{time.Now()}
	// 调用匿名Time上的String方法
	fmt.Println("Full time now:", m.String())
	// 调用myTime.first3Chars
	fmt.Println("First 3 chars:", m.first3Chars())
}

/* Output:
Full time now: Mon Oct 24 15:34:54 Romance Daylight Time 2011
First 3 chars: Mon
*/

链接