很高心,你已经跟着我的学习步伐来到了“方法”的部分,那么Go语言的相关知识你已经基本上完成了快50%的学习了,你已经是一个“半成手”了!
这一章,我们将重点把精力放在 方法 上,这个GO编程语言特有的概念!相信你已经摩拳擦掌,迫不及待了吧,那我们开始吧~
一、方法定义
相信大家学习编程,尤其是90后的我们以及后来的新同学们,都是“面向对象(OOP)编程”,相对于传统的面向过程编程,面向对象编程有很多优势和特点,这里我们就不展开细讲。当然,Go语言也包含了对变相对象编程的支持。
简单理解,就是一个对象(简单的值或者变量),这个对象有自己的属性和方法,这些方法定义了这个对象的相关操作,使用者不直接去操作对象,而是通过这些方法来做,这就是我们以前了解的方式。那么这里的方法,也就是咱们要学习的方法!
Golang 方法总是绑定对象实例,并隐式将实例作为第一实参 ,叫做方法的接收器(receiver)。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。
//方法定义(参数和返回值可以省略)
func (recevier type) methodName(参数列表)(返回值列表){}
//结构体
type User struct {
Name string
Email string
}
//方法
func (u User) Notify() {
fmt.Printf("%v : %v \n", u.Name, u.Email)
}
func main() {
// 值类型调用方法
u1 := User{"golang", "golang@golang.com"}
//u1.Notify这种表达式叫做选择器,即选择u1这个对象的Notify方法执行
u1.Notify()
// 指针类型调用方法
u2 := User{"go", "go@go.com"}
u3 := &u2
u3.Notify()
}
- 只能为当前包内命名类型定义方法
- 参数 receiver 可任意命名。如方法中未曾使用 ,可省略参数名,保持其在方法间传递时的一致性和简短性是不错的主意,一般采用变量的小写首字母
- 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接口或指针
- 不支持方法重载,receiver 只是参数签名的组成部分
- 可用实例 value 或 pointer 调用全部方法,编译器自动转换
二、方法和普通函数的区别
- 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
- 但对于方法(如struct的方法),与普通函数不同,接收者为指针类型和值类型的方法,指针类型和值类型的变量均可相互调用
可以看一个下面的例子:
//1.普通函数
//接收值类型参数的函数
func valueIntTest(a int) int {
return a + 10
}
//接收指针类型参数的函数
func pointerIntTest(a *int) int {
return *a + 10
}
func structTestValue() {
a := 2
fmt.Println("valueIntTest:", valueIntTest(a))
//函数的参数为值类型,则不能直接将指针作为参数传递
//fmt.Println("valueIntTest:", valueIntTest(&a))
//compile error: cannot use &a (type *int) as type int in function argument
b := 5
fmt.Println("pointerIntTest:", pointerIntTest(&b))
//同样,当函数的参数为指针类型时,也不能直接将值类型作为参数传递
//fmt.Println("pointerIntTest:", pointerIntTest(b))
//compile error:cannot use b (type int) as type *int in function argument
}
//2.方法
type PersonD struct {
id int
name string
}
//接收者为值类型
func (p PersonD) valueShowName() {
fmt.Println(p.name)
}
//接收者为指针类型
func (p *PersonD) pointShowName() {
fmt.Println(p.name)
}
func structTestFunc() {
//值类型调用方法
personValue := PersonD{101, "hello world"}
personValue.valueShowName()
personValue.pointShowName()
//指针类型调用方法
personPointer := &PersonD{102, "hello golang"}
personPointer.valueShowName()
personPointer.pointShowName()
}
另外,方法比之函数的一个好处:方法名可以简短。因为不同的对象内部,方法名可以相同,比如Path和Point都有Distance这个方法,不需要用类似PointDistance这样的名字来区分。
三、匿名字段
利用Golang匿名字段,我们可以像字段成员那样访问匿名字段方法,编译器负责查找。通过匿名字段,可获得和继承类似的复用能力。依据编译器查找次序,只需在外层定义同名方法,就可以实现 "override"。
type User struct {
id int
name string
}
type Manager struct {
User
}
func (self *User) ToString() string { // receiver = &(Manager.User)
return fmt.Sprintf("User: %p, %v", self, self)
}
func main() {
m := Manager{User{1, "Tom"}}
fmt.Printf("Manager: %p\n", &m)
fmt.Println(m.ToString())
}
关于方法基础,我们就总结这么多,接下来详细讲一下指针接收者方法。