go 方法
什么是方法
方法其实就是一个函数,在 func
这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的。
在其他语言中,方法是绑定给对象的,在go语言中,方法是绑定给结构体的。
下面就是创建一个方法的语法。
func (t Type) methodName(parameter list) {
}
示例:
func main() {
cat := Cat{}
cat.ChangeName("小猫") // 方法绑定给了Cat结构体,可以通过结构体对象直接调用方法
fmt.Println(cat) // { 0} 下文解释
}
type Cat struct {
name string
age int
}
// 定义一个方法
func (cat Cat)ChangeName(name string) { // 注意: 方法不能与字段名重复
cat.name = name // cat 就是 python 中的 self
}
// 写成下面这样就和python中一样了
func (self Cat)ChangeName(name string) {
self.name = name
fmt.Println(cat) // {小猫 0}
}
方法和函数的区别
方法可以看作是特殊的函数,绑定在了结构体上,方法能实现的,使用函数也能实现,但是方法能够把结构体对象自动传入,而函数需要什么参数全都得传,所以方法相比于函数,使用起来更方便
var cat Cat=Cat{Name: "lxx",age: 38}
// 调用函数(正常调用,有几个值,就要传几个值)
PrintName(person)
// 调用方法(对象.方法,对象自动传入)
person.printName()
有了结构体,有了方法等同于面向对象中 类与对象 能够使用 对象.属性 对象.方法
总结:
Go 不是纯粹的面向对象编程语言
,而且Go不支持类。因此,基于类型的方法是一种实现和类相似行为的途径。- 相同的名字的方法可以定义在不同的类型上,而相同名字的函数是不被允许的。
指针接收器与值接收器
到目前为止,我们只看到了使用值接收器的方法。其实还可以创建使用指针接收器的方法。值接收器和指针接收器之间的区别在于,在指针接收器的方法内部的改变能够影响外部对象,然而值接收器不能影响外部对象。
fmt.Println(cat) // { 0} 下文解释
原因: 接收器跟函数传参一样,都是copy传参,传到方法中修改,不会影响原来的。
func main() {
cat := Cat{}
cat.ChangeName("小猫")
fmt.Println(cat) // { 0}
cat.ChangeName2("白猫")
fmt.Println(cat) // {白猫 0}
}
type Cat struct {
name string
age int
}
// 值接收器
func (cat Cat)ChangeName(name string) { // 注意: 方法不能与字段名重复
cat.name = name
fmt.Println(cat)
}
// 指针接收器
func (cat *Cat)ChangeName2(name string) { // 注意: 方法不能与字段名重复
cat.name = name
fmt.Println(cat) // &{白猫 0}
}
使用指针类型接收器时机
- 当拷贝一个结构体的代价过于昂贵时,可以使用指针类型接收器
- 想修改原来的值,就要使用指针类型接收器
匿名字段的方法
属于结构体的匿名字段的方法可以被直接调用,就好像这些方法是属于定义了匿名字段的结构体一样。因此也可以将其叫做方法提升
func main() {
cat1 := Ccat{"公猫",Cat{"花猫", 12}}
fmt.Println(cat1) // {公猫 {花猫 12}}
cat1.ChangeName2("小花猫")
fmt.Println(cat1) // {公猫 {小花猫 12}}
}
type Cat struct {
name string
age int
}
type Ccat struct {
sex string
Cat
}
func (cat *Cat)ChangeName2(name string) { // 注意: 方法不能与字段名重复
cat.name = name
fmt.Println(cat) // &{花猫属 12}
}
方法中值接收器和函数中值参数
当一个函数有一个值参数,它只能接受一个值参数。
当一个方法有一个值接收器,它可以接受值接收器和指针接收器。
func main() {
cat := Cat{"白猫", 4}
cat1 := &Cat{"白猫", 5}
cat1.ChangeName("黑猫") // 可以传指针类型
cat.ChangeName("黑猫") // 也可以传值类型
ChangeName(cat, "黑猫") // 支持传值类型,但是不支持指针类型
fmt.Println(cat, cat1) // {白猫 4} &{白猫 5}
}
type Cat struct {
name string
age int
}
// 值类型方法
func (cat Cat) ChangeName(name string) {
cat.name = name
fmt.Println(cat) // {黑猫 4}
}
// 接收值类型参数
func ChangeName(cat Cat, name string) {
cat.name = name
fmt.Println(cat) // {黑猫 5}
}
无论是值接收器还是值参数函数, 两者都不会影响原来的数据
方法中指针接收器和函数中指针参数
和值参数相类似,函数使用指针参数只接受指针,而使用指针接收器的方法可以使用值接收器和指针接收器。
func main() {
cat := Cat{"白猫", 4}
cat1 := &Cat{"白猫", 5}
cat1.ChangeName2("黑猫") // 可以传指针类型
cat.ChangeName2("黑猫") // 也可以传值类型
ChangeName1(cat1, "黑猫") // 支持传指针类型,但是不支持值类型
fmt.Println(cat, cat1) // {黑猫 4} &{黑猫 5}
}
type Cat struct {
name string
age int
}
// 指针类型方法
func (cat *Cat) ChangeName2(name string) {
cat.name = name
fmt.Println(cat) // &{黑猫 4}
}
// 接收指针类型参数
func ChangeName1(cat *Cat, name string) {
cat.name = name
fmt.Println(cat) // &{黑猫 5}
}
结论:
- 使用方法比函数方便太多了
- 无论结构体对象是指针还是值,能否影响原数据都取决于方法中接收器的参数类型是值类型还是指针类型,所以方法写成值类型接收器还是指针类型接收器都可以,但是一般把结构体对象做成指针,以减少内存消耗
在非结构体上使用方法
到目前位置,我们只在结构体类型上定义方法。也可以在非结构体类型上定义方法,但是有一个问题。为了在一个类型上定义一个方法,方法的接收器类型定义和方法的定义应该在同一个包中。到目前为止,我们定义的所有结构体和结构体上的方法都是在同一个 main 包中,因此它们是可以运行的。
package main
func (a int) add(b int) { // 报错
}
func main() {
}
在上面程序的第 3 行,我们尝试把一个 add
方法添加到内置的类型 int
。这是不允许的,因为 add
方法的定义和 int
类型的定义不在同一个包中。
可以让该程序工作的方法是为内置类型 int 创建一个类型别名,然后创建一个以该类型别名为接收器的方法。
func main() {
i := Myint8(1)
i.add()
i.add()
fmt.Println(i) // 3
}
type Myint8 int8 // 给Myint8类型绑定add方法
func (i *Myint8) add() {
*i++
}
结构体取代类
类是一系列属性和方法的集合,结构体是一系列属性的集合,结构体+方法=类。
由上文结构体导出可以知道,结构体属性字段, 首字母小写就不能导出到外部包(隐藏属性), 但是我们可以在包内对该结构体通过方法,来实现外部包对内部隐藏属性的修改,案例入下:
test包
package test
import (
"fmt"
)
type Dog struct {
Name string
age int
}
func (dog Dog) ShowAge() {
fmt.Println(dog.age)
}
func (dog *Dog) UpdateAge(age int) {
// 这样使用可以在这里添加校验逻辑
dog.age = age
}
main 包
package main
import test5 "day03/test"
func main() {
dog := &test5.Dog{Name: "白狗"}
dog.UpdateAge(5)
dog.ShowAge() // 5
}