zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Go语言——方法

2023-06-13 09:18:38 时间

方法

□ 概述

本质上,方法是一个和特殊类型关联的函数。

⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:

// 方法
func (receiver ReceiverType) funcName (parameters) (results)
// 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
// 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接⼝或指针。
// 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。

方法与函数的区别:

  1. 函数是⼀段具有独⽴功能的代码,可以被反复多次调⽤,从⽽实现代码复⽤。⽽⽅法是⼀个类的⾏为功能,只有该类的对象才能调⽤。
  2. Go语⾔的⽅法method是⼀种作⽤于特定类型变量的函数,这种特定类型变量叫做Receiver(接受者、接收者、接收器);
  3. 接受者的概念类似于传统⾯向对象语⾔中的this或self关键字;
  4. ⼀个⽅法就是⼀个包含了接受者的函数;
  5. 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}
}