方法
□ 概述
本质上,方法是一个和特殊类型关联的函数。
⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:
// 方法 func (receiver ReceiverType) funcName (parameters) (results) // 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。 // 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接⼝或指针。 // 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
方法与函数的区别:
函数是⼀段具有独⽴功能的代码,可以被反复多次调⽤,从⽽实现代码复⽤。⽽⽅法是⼀个类的⾏为功能,只有该类的对象才能调⽤。
Go语⾔的⽅法method是⼀种作⽤于特定类型变量的函数,这种特定类型变量叫做Receiver(接受者、接收者、接收器);
接受者的概念类似于传统⾯向对象语⾔中的this或self关键字;
⼀个⽅法就是⼀个包含了接受者的函数;
Go语⾔中, 接受者的类型可以是任何类型,不仅仅是结构体, 也可以是struct类型外的其他任何类型。
type Employee struct { name, currency string salary int } //参数为Employee类型 的函数 func displaySalary(e Employee) { fmt.Printf("员工姓名: %s, 薪资: %s%d\n", e.name, e.currency, e.salary) } //接收者类型为Employee 的方法 func (e Employee) displaySalary() { fmt.Printf("员工姓名: %s, 薪资: %s%d\n", e.name, e.currency, e.salary) } func main() { emp1 := Employee{ name: "纱布", salary: 2000, currency: "$", // 货币单位 } emp1.displaySalary() //调用方法 员工姓名: 纱布, 薪资: $2000 displaySalary(emp1) //调用函数 员工姓名: 纱布, 薪资: $2000 }
□ 为类型添加方法
- 基础类型作为接收者
type MyInt int //自定义类型,给int改名为MyInt // 方法 func (a MyInt) Add(b MyInt) MyInt { //面向对象 return a + b } // 传统方式的定义 func Add(a, b MyInt) MyInt { //面向过程 return a + b } func main() { // 基础类型作为接收者 var a MyInt = 1 var b MyInt = 1 //调用func (a MyInt) Add(b MyInt) fmt.Println("a.Add(b) = ", a.Add(b)) //a.Add(b) = 2 //调用func Add(a, b MyInt) fmt.Println("Add(a, b) = ", Add(a, b)) //Add(a, b) = 2 }
通过上面的例子可以看出,面向对象只是换了一种语法形式来表达。
方法是函数的语法糖,因为receiver其实就是方法所接收的第1个参数。
结构体作为接收者
type Person struct { name string sex byte age int } func (p Person) PrintInfo() { //给Person添加方法 fmt.Println(p.name, p.sex, p.age) } func main() { p := Person{"手嘎吧", '男', 18} //初始化 p.PrintInfo() //调用func (p Person) PrintInfo() }
□ 值语义和引用语义
值语义 :值作为接收者,在方法中对对象的改变 出了方法就没用了。
引用语义:指针作为接收者,在方法中对对象的改变 出了方法仍然有效。
type Person34 struct { name string } // 值作为接收者,引用语义 func (p34 Person34) printInfo01() { p34.name = "张删" } // 指针作为接收者,引用语义 func (p34 *Person34) printInfo02() { p34.name = "历时" } func main() { p34:=Person34{"糊涂"} p34.printInfo01() fmt.Println(p34.name) // 糊涂 p34.printInfo02() fmt.Println(p34.name) // 历时 }
□ 方法集
方法集是指可以被该类型的值可调用的所有方法的集合。
类型 *T 方法集(接受者为值或者指针 都可去调用)
一个 指向自定义类型的值 的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。
如果在指针上调用一个接受值的方法,Go语言会聪明地将该指针解引用,并将指针所指的底层值(那片内存)作为方法的接收者。
type Person34 struct { name string } // 指针作为接收者,引用语义 func (p34 Person34) printInfoPrinter() { p34.name = "张删" } // 值作为接收者,引用语义 func (p34 *Person34) printInfoValue() { p34.name = "历时" } func main() { p34:= &Person34{"糊涂"} p34.printInfoPrinter() fmt.Println(p34.name) // 糊涂 (*p34).printInfoValue() fmt.Println(p34.name) // 历时 p34.printInfoValue() fmt.Println(p34.name) // 历时 }
类型 T 方法集(不可直接调接受者类型为指针的方法)
一个 自定义类型值 的 方法集 则由为 该类型定义的接收者类型为值类型的方法组成,但是不包含那些接收者类型为指针的方法。
但这种限制通常并不像这里所说的那样,因为如果我们只有一个值,仍然可以调用一个接收者为指针类型的方法,这可以借助于Go语言传值的地址能力实现。
p34:= Person34{"糊涂"} (&p34).printInfoPrinter() fmt.Println(p34.name) // 历时
□ 方法的继承与重写
- 方法的继承、
如果匿名字段实现了一个方法,那么包含这个匿名字段的struct也能调用该方法。
type Person35 struct { name string } func (p Person35) fatherMethod() { fmt.Println("你继承了父类的方法") } type Student35 struct { Person35 name string } func main() { stu:=Student35{Person35{"糊涂啊"},"糊涂孩纸"} // 继承父类的方法 stu.fatherMethod() }
方法的重写
// 父类方法 func (p Person35) fatherMethod() { fmt.Println(p.name) } // 重写 父类的方法 func (s Student35) rewriteFather() { fmt.Println(s.name) } func main() { per := Person35{"父名"} stu := Student35{Person35{"子名"}, 12} // 重写父类的方法 per.fatherMethod() // 父名 stu.fatherMethod() // 子名 }
□ 表达式
type Person36 struct { id int name string age int } func (p Person36) PrintInfoValue() { fmt.Printf("%p,%v\n", &p, p) } // 建议使用这种指针类型的 func (p *Person36) PrintInfoPointer() { fmt.Printf("%p,%v\n", &p, *p) }
方法值( 隐式传参 )
就是实例化之后的对象去调用 绑定该对象的方法名 这时候结果是一个func()类型的值,这个就是方法值,去赋值给一个变量。
func main() { p := Person36{1, "condition", 18} // 值传递 // 可以看出调用方法名的结果是个地址 说明也可以作为值去传递 fmt.Println(p.PrintInfoPointer) // 0x6f9a00 fmt.Printf("%T\n", p.PrintInfoPointer) // func() //方法值,隐式传递 receiver,绑定实例(对象) //(意思就是这还是个属于Person35的方法,且绑定了实例化对象p,但是你并没有显式的赋予给pFunc1) pFunc1 := p.PrintInfoPointer pFunc1() // 0xc000006038,{1 condition 18} pFunc2 := p.PrintInfoValue pFunc2() // 0xc000042440,{1 condition 18} }
方法表达式(显示传参)
func main() { p := Person36{1, "condition", 18} // 方法表达式 pFunction1 := Person36.PrintInfoValue fmt.Printf("%T\n",pFunction1) // func(main.Person36) // pFunction1() 错误写法:因为没有绑定实例化的对象,需要传入实例化后的对象 pFunction1(p) // 0xc000042480,{1 condition 18} // 不同于方法值的地方 对于参数的类型是严格要求的 要求参数为指针时,就必须为指针。 pFunction2 := (*Person36).PrintInfoPointer fmt.Printf("%T\n",pFunction2) // func(*main.Person36) pFunction2(&p) // 0xc000006038,{1 condition 18} }