在Go
语言中有一个概念和函数极其相似,叫做方法。Go
语言的方法其实是作用在接收者(receiver)
上的一个函数,接收者是某种非内置类型的变量。方法是与对象实例绑定的一种特殊的函数
接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是int
、bool
、string
或数组的别名类型。但是接收者不能是一个接口类型。
方法和函数的区别是:方法在定义的时候,会在func
和方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就和接收者绑定在了一起,称之为这个接收者的方法。
type Person struct {
name string
}
func (p Person) getName() string{
return "the person name is "+p.name
}
留意例子中,func
和方法名之间增加的参数(p Person)
,这个就是接收者。现在我们说,类型person
有了一个getName()
方法,现在我们看下如何使用它:
func main() {
p := Person{name: "张三"}
fmt.Println(p.getName())
}
Go
语言里有两种类型的接收者:
- 值接收者
- 指针接收者
我们上面的例子中,就是使用值类型接收者的示例。
使用值类型接收者定义的方法,在调用的时候,使用的其实是值接收者的一个副本,所以对该值的任何操作,不会影响原来的类型变量。
package main
import "fmt"
type Person struct {
name string
}
func (p Person) getName() string {
return "the person name is " + p.name
}
func (p Person) changeName() string {
p.name = "zhaosi"
return p.name
}
func main() {
p := Person{name: "张三"}
fmt.Println(p.getName())
fmt.Println(p.changeName())
fmt.Println(p.getName())
}
执行结果:
the person name is 张三
zhaosi
the person name is 张三
以上的例子,打印出来的值还是张三,对其进行的修改无效。如果我们使用一个指针作为接收者,那么就会其作用了,因为指针接收者传递的是一个指向原值指针的副本,指针的副本,指向的还是原来类型的值,所以修改时,同时也会影响原来类型变量的值:
package main
import "fmt"
type Person struct {
name string
}
func (p Person) getName() string {
return "the person name is " + p.name
}
// 只是改了这里,改成了Person指针的方法
func (p *Person) changeName() string {
p.name = "zhaosi"
return p.name
}
func main() {
p := Person{name: "张三"}
fmt.Println(p.getName())
// 或者这样写也可以fmt.Println((&p).changeName()),方法的调用,既可以使用值,也可以使用指针,我们不必要严格的遵守这些,Go语言编译器会帮我们进行自动转义的,这大大方便了我们开发者。
fmt.Println(p.changeName())
fmt.Println(p.getName())
}
执行结果:
the person name is 张三
zhaosi
the person name is zhaosi
从上面可以看到只需要改动一下,变成指针的接收者,就可以完成了修改。
在调用方法的时候,传递的接收者本质上都是副本,只不过一个是这个值副本,一是指向这个值指针的副本。指针具有指向原有值的特性,所以修改了指针指向的值,也就修改了原有的值。我们可以简单的理解为值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法。
在Go
中,(接收者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。
方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。
注意:Go
语言不允许为简单的内置类型添加方法,所以下面定义的方法是非法的。
package main
import (
"fmt"
)
func Add(a, b int) { //函数合法
fmt.Println(a + b)
}
func (a int) Add(b int) { //方法非法!不能是内置数据类型
fmt.Println(a + b)
}
这个时候我们需要用Go
语言的type
,来临时定义一个和int
具有同样功能的类型。这个类型不能看成是int
类型的别名,它们属于不同的类型,不能直接相互赋值。
修改后合法的方法定义如下:
package main
import (
"fmt"
)
type myInt int
func Add(a, b int) { //函数
fmt.Println(a + b)
}
func (a myInt) Add(b myInt) { //方法
fmt.Println(a + b)
}
func main() {
a, b := 3, 4
var aa, bb myInt = 3, 4
Add(a, b)
aa.Add(bb)
}
上面的表达式aa.Add
称作选择子(selector)
它为接收者aa
选择合适的Add
方法。
Go
语言不像其它面相对象语言一样可以写个类,然后在类里面写一堆方法,但其实Go
语言的方法很巧妙的实现了这种效果:我们只需要在普通函数前面加个接受者(receiver
,写在函数名前面的括号里面),这样编译器就知道这个函数(方法)属于哪个struct
了。例如:
package main
import (
"fmt"
)
type A struct {
Name string
}
func (a A) foo() { //接收者写在函数名前面的括号里面
fmt.Println("foo")
}
func main() {
a := A{}
a.foo() //foo
}
最后插一点知识,上面再讲方法的时候用到了type myInt int
,这是干什么用的呢?这里是类型等价定义,相当于类型重命名,只要这样定以后myInt
类型就与int
等价。
那type
到底有哪些作用呢? 这里总结一下:
- 定义结构体
type Person struct {
name string
age int
}
- 类型等价定义,相当于类型重命名
type name string
func main() {
var myname name = "taozs" //其实就是字符串类型
l := []byte(myname) //字符串转字节数组
fmt.Println(len(l)) //字节长度
}
- 定义接口
type Personer interface {
Run()
Name() string
}
- 定义函数类型
type handler func(name string) int
针对这个函数类型可以再定义方法,如:
func (h handler) add(name string) int {
return h(name) + 10
}
继承是通过匿名字段的方式实现。
type Person struct {
name string
age int
}
func (person Person) Print() {
fmt.Println("print :", person)
}
type Student struct {
Person // 继承了Person里面的成员和方法
byte
class string
}
func main() {
s := Student{Person{"hello", 1}, 'm', "幼儿园"}
// Print()方法是Person类的,但是Student继承了Person类,也就有了Print方法
s.Print()
}
执行结果:
print : {hello 1}
type Person struct {
name string
age int
}
func (person Person) Print() {
fmt.Println("print :", person)
}
type Student struct {
Person // 只有类型,没有名字就叫做匿名字段,继承了Person里面的成员
byte
class string
}
// 方法名一样,就是重写
func (student Student) Print() {
fmt.Println("print :", student)
}
func main() {
s := Student{Person{"hello", 1}, 'm', "幼儿园"}
s.Print()
}
执行结果:
print : {{hello 1} 109 幼儿园}
- 邮箱 :[email protected]
- Good Luck!