前言
一个函数是没有接收方
的。但当一个函数有了接收方
后,则称之为方法
。
在python中,一个类(接收方)定义方法,直接在类里写方法即可,只不过有一个self
指向了它本身。这样,一个类对象就通过方法实现了行为。
在go中,没有类概念,但当自定义的结构
作为方法
的接收方
后,结构
就既有了属性,又有了行为,从而实现了面向对象编程中的部分功能。
✨并非只有自定义结构可以声明方法,其它类型也可以。
定义
func (<变量> <接收方类型>) 方法名(形参) 方法返回类型{
...
}
上面可以看出,方法和函数定义的区别,就是函数名
前面添加了一个(变量 接收方类型)
变量。
这里的变量
仅用于方法内接收方
调用自身属性所用。
💥接收方类型必须先创建,即结构必须先创建。
💥方法的定义不能跨包,也就是说必须和接收方在一个包内。
✨方法可以绑定到多个结构类型上。
例子
package main
import "fmt"
// 定义所属者人类
type person struct {
name string
}
// 定义人类的方法 change
func (self person) change(){
self.name = "凡尔赛."+self.name
fmt.Printf("你的新名字可能变成了:%s\n", self.name)
}
// 主函数
func main(){
// 声明人类变量 p
p := person{name:"张三"}
// 调用 change 方法
p.change()
fmt.Printf("你的新名字变更失败,它依然是:%s\n", p.name)
}
上面的例子,有一个现象,就是change()
并不能变更person.name
因为方法的接收方person
是值
类型,所以p.change()
其实是将p复制了一份传递给了方法的self
变量。因p和self的内存地址
不一样,所以修改self.name
不会改变p.name
。
如何改变?
- 将方法内修改后的值通过 return 直接返回
package main
import "fmt"
// 定义所属者人类
type person struct {
name string
}
// 定义人类的方法 change
func (self person) change() string{
self.name = "凡尔赛."+self.name
fmt.Printf("你的新名字可能变成了:%s\n", self.name)
return self.name
}
// 主函数
func main(){
// 声明人类变量 p
p := person{name:"张三"}
// 调用 change 方法
p.name = p.change()
fmt.Printf("你的新名字是:%s\n", p.name)
}
指针接收方
将方法的接收者变更为指针接收方。
func (self *person) change(){
self.name = "凡尔赛."+self.name
fmt.Printf("你的新名字变成了:%s\n", self.name)
}
func main(){
p := person{name:"张三"}
(&p).change()
fmt.Printf("你的新名字是:%s\n", p.name)
}
什么时候会用到方法中的指针?
- 参数太大,避免复制
💥根据go的约定,要么结构的所有方法接收方都是指针类型,要么都是值类型,不要混用。
嵌套方法
结构可以嵌套另一个结构,外层结构可以直接调用嵌套结构的方法
package main
import "fmt"
type triangle struct {
size int
}
type coloredTriangle struct {
triangle
color string
}
func (t *triangle) perimeter() int {
return t.size * 3
}
func (t *triangle) doubleSize() {
t.size *= 2
}
func main() {
t := coloredTriangle{
triangle{3},
"blue",
}
t.doubleSize()
fmt.Println("NewSize:", t.size)
fmt.Println("Perimeter:", t.perimeter())
}
OldSize: 3
NewSize: 6
Perimeter: 18
coloredTriangle 之所以可以调用 triangle 的方法 doubleSize 和 perimeter,原因在于 go 编译器自动创建了两个包装方法(包装器)。
func (t *coloredTriangle) doubleSize() {
t.triangle.size = t.triangle.doubleSize()
}
func (t *coloredTriangle) perimeter() int {
return t.triangle.perimeter()
}
重载方法
重载方法:表现上就是相同的方法名,不同的接收方。结构对象通过接收方来区分调用的方法。
通过重载方法,可以在不修改主代码的情况下,改变主代码的输出。
以上面的例子来说,就是主动去写包装方法,覆盖掉嵌套时候自动创建的包装方法。
package main
import "fmt"
type triangle struct {
size int
}
type coloredTriangle struct {
triangle
color string
}
func (t *triangle) perimeter() int {
return t.size * 3
}
// 重载方法perimeter
func (t *coloredTriangle) perimeter() int {
return t.size * 3 * 2
}
func main() {
t := coloredTriangle{
triangle{3},
"blue",
}
fmt.Println("Size:", t.size)
fmt.Println("Color Perimeter:", t.perimeter()) // 调用外结构的重载方法。
fmt.Println("NoColor Perimeter:", t.triangle.perimeter()) // 显式的调用嵌入结构的方法。
}
Size: 3
Color Perimeter: 18
NoColor Perimeter: 9
封装方法
封装可以让方法或者属性变成私有,即仅可用于包内部。通过包的封装方法,可以仅提供部分方法供调用包的程序所用。
go的封装方法仅用于程序包之间。
将方法或者属性名的首字母大写即为公开,小写即为私有。
➜ tree
.
├── geometry
│ ├── geometry.go
│ └── go.mod # go mod init geometry
├── go.mod # go mod init 022
└── main.go
➜ cat go.mod
module 022
go 1.17
require geometry v0.0.0
replace geometry => ./geometry
//geometry.go
package geometry
type Triangle struct {
size int
}
func (t *Triangle) doubleSize() {
t.size *= 2
}
func (t *Triangle) SetSize(size int) {
t.size = size
}
func (t *Triangle) Perimeter() int {
t.doubleSize()
return t.size * 3
}
//main.go
package main
import (
"fmt"
"geometry"
)
func main(){
t := geometry.Triangle{}
t.SetSize(3)
fmt.Println("Perimeter", t.Perimeter())
//fmt.Print(t.size)
}
➜ go run main.go
Perimeter 18
如果调用geometry私有属性size,则会报错
# command-line-arguments
./main.go:12:18: t.size undefined (cannot refer to unexported field or method size)
其它类型的方法
默认情况下,无法基于基础类型声明方法,例如无法直接给string类型声明方法。
但是可以基于基础类型创建新的类型,然后针对新的类型声明方法
package main
import (
"fmt"
"strings"
)
type stringupper string
func(s stringupper) Upper() string {
return strings.ToUpper(string(s)) // s 是 stringupper 类型,因此需要转成 string 类型,才可以被 ToUpper 方法调用
}
func main(){
s := stringupper("this is string") // stringupper 类型拥有 string() 方法,因此直接()传递字符串.
fmt.Println(s)
fmt.Println(s.Upper())
}
➜ go run 023.go
this is string
THIS IS STRING