介绍
方法能给用户定义的类型添加新的行为。方法实际上也是函数,只是在声明时,在关键字 func 和方法名之间增加了一个参数。接收者
使用变量调用方法
使用
package main
import "fmt"
// user 在程序里定义了一个用户类型
type user struct {
name string
email string
}
// notify 使用值接收者实现了一个方法
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",
u.name,
u.email)
}
// changeEmail 使用指针接收者实现了一个方法
func (u *user) changeEmail(email string) {
u.email = email
}
// main 是主程序入口
func main() {
// user 类型的值可以用来调用 使用值接收者声明的方法
bill := user{"Bill", "bill@email.com"}
bill.notify()
// 指向 user 类型值的指针也可以用来调用 使用值接收者声明的方法
lisa := &user{"Lisa", "lisa@email.com"}
lisa.notify()
// user 类型的值可以用来调用 使用指针接收者声明的方法
bill.changeEmail("bill@newdomain.com")
bill.notify()
// user 类型的指针可以用来调用 使用指针接收者声明的方法
lisa.changeEmail("lisa@comcast.com")
lisa.notify()
/*
输出
Sending User Email To Bill<bill@email.com>
Sending User Email To Lisa<lisa@email.com>
Sending User Email To Bill<bill@newdomain.com>
Sending User Email To Lisa<lisa@comcast.com>
*/
}
值接收者和指针接收者
1、通过值接收者实现一个方法
调用时会使用这个值的副本来执行(相当于复制)
2、通过指针接收者实现一个方法
当使用指针接收者声明的方法时,这个方法会共享调用方法时接收者所指向的值。
(*lisa).notify() -> lisa.notify()
Go语言在背后将指针解引用为值。
使用值接收者还是指针接收者,不应该由该方法是否修改了接收到的值来决定,这个决策应该基于该类型的本质。
这条规则有一个例外,当需要让类型值符合某个接口的时候,即便类型的本质是非原始的,也可以选择使用值接收者声明方法。
- 内置类型:数值类型,字符串类型,布尔类型,这些类型本质上是原始的类型,因此,当对这些值进行增加或者删除的时候,会创建一个新值。因此这些类型的值传递给方法或者函数时,应该传递一个对应值的副本。使用值接收者传递副本。
- 引用类型:切片、映射、通道、接口和函数类型,它们的内存分配时连续的,创建这些类型的变量时,被称作标头值(head)(首地址),每个引用类型创建的标头值是包含一个指向底层数据结构的指针,每个引用类型还包含一组独特的字段,用于管理底层数据结构。标头值里包含一个指针,因此通过复制来传递一个引用类型的值的副本,本质上就是在共享底层数据结构。使用值接收者传递副本。
- 结构类型:结构类型可以用来描述一组数据值,这组值的本质即可以是原始的,也可以是非原始的。大多数情况下结构体的本质是非原始的,特别是内嵌类型结构,对这个类型的值做增加或者删除操作的时候应该更该值本身。使用指针接收者。
方法注意事项
1、结构体类型是值类型,在方法调用中,遵守值类型的传递机制(值拷贝)
2、也可以使用结构体指针的方式传递
3、方法作用在指定的数据类型上(和指定的数据类型绑定在一起),因此自定义类型都可以有方法,不仅仅是struct,比如 int , float32 … 都可以有方法
4、方法名大写的方法可以在别的包使用,否则只能在本包使用
5、如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()方法输出。
package main
import "fmt"
type Student struct {
Name string
Age int
}
// 给 Student 结构体 实现 String() 方法
func (stu *Student) String() string {
str := fmt.Sprintf("Name=[%v] Age=[%v]", stu.Name, stu.Age)
return str
}
func main() {
// 定义 Student 变量
stu := Student{
Name: "tom",
Age: 20,
}
fmt.Println("stu = ", stu) // {tom 20}
// 如果实现了 *Student类型的 String() 方法,fmt.Println就会自动调用
fmt.Println("stu = ", &stu) // Name=[tom] Age=[20]
}