第六章(方法)[上]

  • 方法是与对象实例绑定的特殊函数

  • 方法和函数定义语法区别的在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。

  • 可以为当前包,以及除接口和指针以外的任何类型定义方法

type N int

func (n N) toString() string {
	return fmt.Sprintf("%#x", n)
}

func main() {
	var a N = 1
	println(a.toString())//0x1
}
  • 当定义一个类型为指针的变量时。接收者(receiver)为指针类型报错
type Student struct {
	age  int8
	name string
}
type StudentPoint *Student

// Error: 接收者(receiver)为指针类型
func (s StudentPoint) showName2() {
	fmt.Println(s.name)
}
  • 方法同样不支持重载(overload)

方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数

  • receiver的类型是指针时
type N int

func (n N) toString() string {
	return fmt.Sprintf("%#x", n)
}

func (n N) value() { //值复制
	n++
	fmt.Printf("v: %p, %v\n", &n, n)
}

func (n *N) pointer() { //指针传递
	*n++
	fmt.Printf("p: %p, %v\n", n, *n)
}

func main() {
	var a N = 1
	a.value()                        //v: 0xc0000180c0, 2
        fmt.Printf("a: %p, %v\n", &a, a) //a: 0xc0000180a8, 1
	a.pointer()                      //p: 0xc0000180a8, 2
	fmt.Printf("a: %p, %v\n", &a, a) //a: 0xc0000180a8, 2
}
  • 使用实例值或指针调用方法时,编译器会根据方法receiver类型自动在基础类型和指针类型间自动转换
func main() {
	var a N = 1
	p := &a
	a.value()   //v: 0xc0000180c0, 2
	a.pointer() //p: 0xc0000180a8, 2

	p.value()   //v: 0xc0000180f0, 3  //自动转换 (*p).value()
	p.pointer() //p: 0xc0000180a8, 3  //自动转换 (*p).pointer()
}

  • 不能使用多级指针调用方法

  • 指针类型receiver必须是合法指针(包括nil)或能获取实例地址

type X struct{}

func (x *X) test() {
	println("Hi!", x)
}
func main() {
	var a1 *X
	a1.test() //Hi! 0x0  --- 0x0就是nil的地址
	//X{}.test() //cannot call pointer method on X{}
}
  • 如何选择方法的receiver类型

    • 要修改实例状态,用*T
    • 无须需改状态的小对象或固定值,建议用T
    • 大对象建议用*T,以减少复制成本
    • 引用类型、字符串,函数等指针包装对象,直接用T
    • 若包含Mutex等同步字段,用*T,避免因复制造成锁操作无效
    • 其它无法确定的情况,都用*T
  • 可以像访问匿名字段成员那样调用其方法,由编译器负责查找

type data struct {
	sync.Mutex
	buf [1024]byte
}

func main() {
	d := data{}
	d.Lock() //编译器会处理成 sync.(*Mutex).Lock 调用
	defer d.Unlock()
}
  • 方法会有同名遮蔽问题,利用该特性可实现类似覆盖(override)操作

方法覆盖(Overriding): 如果在子类中定义一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法覆盖了父类的方法。

type user struct{}
type manager struct {
	user
}

func (user) toString() string {
	return "user"
}
func (m manager) toString() string {
	return m.user.toString() + ";manager"
}

func main() {
	var m manager
	println(m.toString())      //user;manager
	println(m.user.toString()) //user
}

注意:尽管能直接访问匿名字段的成员和方法,但不属于继承关系

  • 方法集
    • 类型T方法集包含所有receiver T 方法
    • 类型*T方法集包含所有receiver T + *T 方法
    • 匿名嵌入S,T方法集包含所有receiver S 方法
    • 匿名嵌入*S,T方法集包含所有receiver S + *S方法
    • 匿名嵌入S或*S,*T方法集包含所有receiver S + *S方法
package main

import (
	"fmt"
)

type Student struct {
	age  int8
	name string
}
type StudentPoint *Student

func (Student) sayHello() {
	fmt.Println("hello world")
}
func (s Student) showName() {
	fmt.Println(s.name)
}
func (s *Student) sayHi() {
	fmt.Println("Hi")
}
func (s *Student) setName(newName string) {
	s.name = newName
}

// Error: 接收者(receiver)为指针类型
/*func (s StudentPoint) showName2() {
	fmt.Println(s.name)*/
}
func main() {
	s := Student{}
	// go会自动转为(&s).setName("dq")
	s.setName("dq")
	s.sayHi() // s 可以调用*s 方法
	var s2 = &s
	// go会自动转为(*s2).showName()
	s2.showName()
}

  • 上面可以用 s 调用 *s 的方法?

实例value(普通值传递)或pointer(指针)可以调用全部的方法,编译器会自动转换。(方法集只对接口interface起作用)

  • 方法集仅影响接口实现和方法表达式转换,与通过实例或实例指针调用方法无法。

面向对象三大特性:封装、继承、多态。go仅实现了部分特征,它更倾向于“组合优于继承”这种思想。

组合没有父子依赖,不会破坏封装。且整体和局部松耦合,可任意增加来实现扩展。各单元特有单一职责,互不关联,实现和维护更加简单

posted @ 2023-01-12 23:10  巴达克  阅读(19)  评论(0编辑  收藏  举报